Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
26.38% covered (danger)
26.38%
1639 / 6212
46.26% covered (warning)
46.26%
68 / 147
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiController
26.33% covered (danger)
26.33%
1634 / 6207
46.26% covered (warning)
46.26%
68 / 147
979773.48
0.00% covered (danger)
0.00%
0 / 1
 beforeFilter
0.00% covered (danger)
0.00%
0 / 52
0.00% covered (danger)
0.00%
0 / 1
2
 countUnreadSpotlightPost
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
1 / 1
1
 checkSession
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
6
 generatePeerID
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 getEndTime
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
42
 getNowTime
39.23% covered (warning)
39.23%
71 / 181
0.00% covered (danger)
0.00%
0 / 1
437.95
 isSequenceOf
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
6
 checkForMaintenance
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
1 / 1
4
 checkOngoingMaintenance
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 getLessonStatus
0.00% covered (danger)
0.00%
0 / 208
0.00% covered (danger)
0.00%
0 / 1
4422
 saveAudioMemcached
0.00% covered (danger)
0.00%
0 / 51
0.00% covered (danger)
0.00%
0 / 1
56
 addTeacherStatusLog
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 getProfile
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
30
 getHeader
0.00% covered (danger)
0.00%
0 / 48
0.00% covered (danger)
0.00%
0 / 1
72
 getMemo
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
156
 outputMemo
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 getTransfer
77.78% covered (success)
77.78%
7 / 9
0.00% covered (danger)
0.00%
0 / 1
4.18
 setTransfer
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
1 / 1
5
 updateTransfer
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
56
 deleteTransfer
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
30
 triggerLessonNoteValidation
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
5
 saveTeacherLearnings
0.00% covered (danger)
0.00%
0 / 324
0.00% covered (danger)
0.00%
0 / 1
16256
 SendEditMessageSlack
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 1
12
 arrReqValIsOk
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
7
 saveTeacherMemo
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
12
 setMemo
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
210
 outputSetMemo
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 getAnnounceCount
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
1
 getRequestCount
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
1
 getNextReservationTime
98.21% covered (success)
98.21%
55 / 56
0.00% covered (danger)
0.00%
0 / 1
12
 checkIfMealBreakTime
97.44% covered (success)
97.44%
38 / 39
0.00% covered (danger)
0.00%
0 / 1
11
 setLessonMemo
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
72
 createTeacherStatusLog
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
4
 getUnviewedNotice
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getUnviewedRule
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 askHelp
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
42
 getBreaks
0.00% covered (danger)
0.00%
0 / 83
0.00% covered (danger)
0.00%
0 / 1
420
 checkCanBreak
0.00% covered (danger)
0.00%
0 / 73
0.00% covered (danger)
0.00%
0 / 1
210
 checkCanBreakSlot
0.00% covered (danger)
0.00%
0 / 70
0.00% covered (danger)
0.00%
0 / 1
272
 countTeacherOnBreaks
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
1
 getReservedOrAvailableOnMealBreak
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
3.00
 canLesson
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
4
 studentDisconnection
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
3
 setValidLesson
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
2
 countUnreadAppreciationMessage
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 countUnsentMessage
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
4
 getlastCallanStage
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
10
 callStartTeacherApi
0.00% covered (danger)
0.00%
0 / 311
0.00% covered (danger)
0.00%
0 / 1
13572
 saveLessonConnectionType
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
30
 loadPreviousChatMessages
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
4
 checkSlotHasReservation
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
5
 reviveSessionChecker
75.81% covered (success)
75.81%
47 / 62
0.00% covered (danger)
0.00%
0 / 1
35.57
 syncScheduleFromAccounting
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getUnlessonReservationAndLessonFinishTime
100.00% covered (success)
100.00%
42 / 42
100.00% covered (success)
100.00%
1 / 1
4
 finishUnlessonReservation
79.37% covered (success)
79.37%
50 / 63
0.00% covered (danger)
0.00%
0 / 1
6.32
 getReservationAndCancelTime
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
6
 getRLSShowModalTime
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
4
 cancelReservation
0.00% covered (danger)
0.00%
0 / 65
0.00% covered (danger)
0.00%
0 / 1
156
 getStudentDisconnectionTime
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
5
 finishStatusChange
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
4
 saveImageLink
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
6
 get3MinutesBreakRemainingTime
0.00% covered (danger)
0.00%
0 / 102
0.00% covered (danger)
0.00%
0 / 1
420
 getImpendingAndOngoingReservation
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 deleteMemMinutesBreakRemainingTime
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 saveMemo
0.00% covered (danger)
0.00%
0 / 54
0.00% covered (danger)
0.00%
0 / 1
870
 saveSelectedTextbook
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
8
 saveSelectedTextbookLessonOnAir
100.00% covered (success)
100.00%
31 / 31
100.00% covered (success)
100.00%
1 / 1
4
 saveSelectedTextbookLessonOnAirLogs
100.00% covered (success)
100.00%
31 / 31
100.00% covered (success)
100.00%
1 / 1
4
 updateMemo
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
7
 lessonPeriod
100.00% covered (success)
100.00%
57 / 57
100.00% covered (success)
100.00%
1 / 1
7
 checkTimezone
0.00% covered (danger)
0.00%
0 / 119
0.00% covered (danger)
0.00%
0 / 1
420
 updateTeacherTimezone
100.00% covered (success)
100.00%
26 / 26
100.00% covered (success)
100.00%
1 / 1
6
 getServerAndLocalTime
0.00% covered (danger)
0.00%
0 / 53
0.00% covered (danger)
0.00%
0 / 1
20
 checkDaylightSavingTime
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 getOpenedSlot
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 deleteEqualTo5MinutesAndBelowModal
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
3
 updateCounselingAttendedFlg
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 checkReservation
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
4
 sanitizeInputs
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 getPopupList
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 getTextbookCategoryId
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
 postSlack
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 1
42
 sendChivoxSlack
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 saveAdminStaffEvaluation
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
20
 getAnnouncement
0.00% covered (danger)
0.00%
0 / 52
0.00% covered (danger)
0.00%
0 / 1
42
 getAnnouncementRead
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
4
 getLessonRequest
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 translateText
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 checkTeacherEptSetting
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 lessonOnAirRequestLessonTime
0.00% covered (danger)
0.00%
0 / 136
0.00% covered (danger)
0.00%
0 / 1
2862
 getDateStatus
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
2
 checkFRSendSlack
0.00% covered (danger)
0.00%
0 / 162
0.00% covered (danger)
0.00%
0 / 1
156
 lessionUnitPrice
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
5
 studentYearlySpeakingTest
75.00% covered (success)
75.00%
9 / 12
0.00% covered (danger)
0.00%
0 / 1
11.56
 uploadRecordedFile
0.00% covered (danger)
0.00%
0 / 109
0.00% covered (danger)
0.00%
0 / 1
992
 uploadRecordedFileChivox
0.00% covered (danger)
0.00%
0 / 350
0.00% covered (danger)
0.00%
0 / 1
9702
 saveConnectionSpeed
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
6
 disconnectionCancellationMemCached
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
5
 uploadAttachedFileToAWS
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
30
 downloadChatLogFiles
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
20
 uploadeCancelDeleteFile
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 uploadRecordedFileMerge
0.00% covered (danger)
0.00%
0 / 244
0.00% covered (danger)
0.00%
0 / 1
3306
 uploadRecordedFileMergeAsync
0.00% covered (danger)
0.00%
0 / 233
0.00% covered (danger)
0.00%
0 / 1
3660
 getTimezoneUserAndTeacher
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
2
 uploadRecordedFileAsync
0.00% covered (danger)
0.00%
0 / 107
0.00% covered (danger)
0.00%
0 / 1
1122
 debugSocketLog
0.00% covered (danger)
0.00%
0 / 48
0.00% covered (danger)
0.00%
0 / 1
90
 logTeacherDebugError
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 sendSlackDebugMessage
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
3
 saveSkywayDebug
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 reCheckReservedLesson
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 virtualCameraAllow
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
4
 getTextbookEquivalentScores
0.00% covered (danger)
0.00%
0 / 42
0.00% covered (danger)
0.00%
0 / 1
132
 confirmSuddenLesson
0.00% covered (danger)
0.00%
0 / 96
0.00% covered (danger)
0.00%
0 / 1
306
 postEventCampaign
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
132
 getTeachinMaterialEngName
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
5
 teacherAudioDataMemCached
98.18% covered (success)
98.18%
54 / 55
0.00% covered (danger)
0.00%
0 / 1
11
 nextLessonReservation
0.00% covered (danger)
0.00%
0 / 63
0.00% covered (danger)
0.00%
0 / 1
156
 otherAudioDataMemCached
0.00% covered (danger)
0.00%
0 / 54
0.00% covered (danger)
0.00%
0 / 1
182
 fcmWebNotification
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
20
 studentLeftLessonUpdate
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
8
 updateCurrentTutorialModalSteps
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
1 / 1
9
 getUnviewedWarning
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 showReservedLessonSpecialModal
94.44% covered (success)
94.44%
17 / 18
0.00% covered (danger)
0.00%
0 / 1
5.00
 getLearningKitCount
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 dynamicModalData
0.00% covered (danger)
0.00%
0 / 61
0.00% covered (danger)
0.00%
0 / 1
72
 saveBookmark
0.00% covered (danger)
0.00%
0 / 161
0.00% covered (danger)
0.00%
0 / 1
870
 saveCallanStage
0.00% covered (danger)
0.00%
0 / 80
0.00% covered (danger)
0.00%
0 / 1
240
 fetchBookmark
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
6
 fetchBookmarkTextbook
100.00% covered (success)
100.00%
54 / 54
100.00% covered (success)
100.00%
1 / 1
5
 saveNewCallanLevelCheck
0.00% covered (danger)
0.00%
0 / 173
0.00% covered (danger)
0.00%
0 / 1
650
 getStageConnectID
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
1 / 1
3
 recordPlayedPhrase
100.00% covered (success)
100.00%
30 / 30
100.00% covered (success)
100.00%
1 / 1
5
 gameGetPlayersProfile
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
1 / 1
6
 saveGameResult
97.78% covered (success)
97.78%
44 / 45
0.00% covered (danger)
0.00%
0 / 1
8
 saveGameDataMemcache
99.00% covered (success)
99.00%
99 / 100
0.00% covered (danger)
0.00%
0 / 1
21
 gameDataMemcache
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
3.00
 getCurrentChatHash
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 googleCalendarCb
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 1
42
 googleCalendarFlagUpdate
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
42
 unlinkGoogleCalendar
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 isUserChocottoCamp
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getUploadRecordedFileAsync
46.00% covered (warning)
46.00%
23 / 50
0.00% covered (danger)
0.00%
0 / 1
44.86
 googleConvertedFileUpload
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
56
 googleConvertedSpeech
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 1
72
 getLabelTranslations
100.00% covered (success)
100.00%
26 / 26
100.00% covered (success)
100.00%
1 / 1
1
 sendSlackStreamCheckError
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
3
 lessonChatHashChecker
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2App::uses('AwsFileServer', 'Lib');
3App::uses('AppController', 'Controller');
4App::uses('LessonMessageController', 'Controller');
5App::uses('firebaseCloudMsg', 'Lib');
6App::uses('GoogleCalendar','Lib');
7class ApiController extends AppController{
8
9    public $uses = array(
10        'LessonOnair',
11        'Post',
12        'UsersClass',
13        'LessonText',
14        'User',
15        'LessonUserOnair',
16        'Teacher',
17        'LessonTransfer',
18        'LessonSchedule',
19        'LessonScheduleCancel',
20        'LessonMemo',
21        'TeacherStatusLog',
22        'UsersLessonLearning',
23        'Notification',
24        'TeacherStatus',
25        'TeacherBreaktimeDetail',
26        'LessonOnair',
27        'LessonOnairsLog',
28        'ChatHistory',
29        'ShiftWorkOn',
30        'BreakLimit',
31        'TeacherMonitorImages',
32        'TeacherRankCoin',
33        'Timezone',
34        'Workstation',
35        'LessonPopup',
36        'TextbookConnect',
37        'ProhibitedWord',
38        'AdminStaffEvaluation',
39        'TeacherAnnouncement',
40        'TeacherAnnouncementsRead',
41        'LessonRequest',
42        'ShiftWorkMealBreak',
43        'FaceRegistration',
44        'FaceRecognition',
45        'SettingOption',
46        'LessonConnectionSpeedLog',
47        'AnnounceTeacherGroup',
48        'TeacherGroupLink',
49        'UsersChivoxCountQuery',
50        'Nationality',
51        'OfficeMessageIncentive',
52        'FamilyPlanList',
53        'TeacherCoinBox',
54        'Payment',
55        'LessonOnairsLog',
56        'TeachersDebugSkyway',
57        'HomeBasedReserveCoinData',
58        'HomeBasedFinalSalaryData',
59        'HomeBasedRankBasicAmount',
60        'HomeBasedTeacherRankLog',
61        'Warning',
62        'TeacherTimeSetting',
63        'UsersExtend',
64        'TrainingData',
65        'TeacherBadge',
66        'TeacherAvatarCameraAllowed',
67        'CharacterSettings',
68        'LessonBookmark',
69        'LessonBookmarkHistory',
70        'CallanBookmarkOption',
71        'UserNewLessonCallanProgress',
72        'TeacherAccessToken',
73        'UserDiscountOptionsTerm',
74        'LessonSchedulesExtend',
75        'NCContentList',
76        'TeacherSpotlightReaction',
77        'TeacherSpotlightView',
78        'TeacherSpotlight',
79        'UsersDetail'
80    );
81
82    public $components = array('Session');
83    public $homeBase = NULL;
84    public function beforeFilter() {
85        parent::beforeFilter();
86        //instantiate slack
87        $this->mySlack = new mySlack();
88        $this->Auth->allow(
89            'checkSession',
90            'getNowTime',
91            'getLessonStatus',
92            'loadPreviousChatMessages',
93            'callStartTeacherApi',
94            'reviveSessionChecker',
95            'studentDisconnection',
96            'getStudentDisconnectionTime',
97            'cancelReservation',
98            'saveAdminStaffEvaluation',
99            'getAnnouncement',
100            'getLessonRequest',
101            'translateText',
102            'lessonOnAirRequestLessonTime',
103            'uploadRecordedFile',
104            'uploadRecordedFileChivox',
105            'saveSelectedTextbook',
106            'saveConnectionSpeed',
107            'downloadChatLogFiles',
108            'uploadeCancelDeleteFile',
109            'checkForMaintenance',
110            'checkOngoingMaintenance',
111            'sendChivoxSlack',
112            'getTimezoneUserAndTeacher',
113            'uploadRecordedFileAsync',
114            'debugSocketLog',
115            'logTeacherDebugError',
116            'saveSkywayDebug',
117            'postEventCampaign',
118            'getTeachinMaterialEngName',
119            'fcmWebNotification',
120            'generatePeerID',
121            'uploadRecordedFileMergeAsync',
122            'studentLeftLessonUpdate',
123            'countUnsentMessage',
124            'saveTeacherLearnings',
125            'getRequestCount',
126            'googleCalendarCb',
127            'googleCalendarFlagUpdate',
128            'unlinkGoogleCalendar',
129            'saveBookmark',
130            'saveCallanStage',
131            'fetchBookmark',
132            'saveGameDataMemcache',
133            'gameDataMemcache',
134            'countUnreadSpotlightPost',
135            'sendSlackStreamCheckError'
136        );
137
138        $this->homeBase = $this->Auth->user('home_flg');
139    }
140
141    /**
142     * @api {get} /teacher/api/countUnreadSpotlightPost countUnreadSpotlightPost
143     * @apiName countUnreadSpotlightPost
144     * @apiGroup API
145     * @apiDescription Counts the number of unread spotlight posts for the teacher.
146     * @apiSampleRequest off
147     * 
148     * @apiSuccess {Number} count Returns the count of unread spotlight posts.
149     * 
150     * @apiErrorExample {json} Success-Response:
151     *     {
152     *       "count": 5
153     *     }
154     * 
155      * @apiErrorExample {js} Used in: AngularJS
156      * Location: "webroot/js/ng/app.js"
157     */
158    public function countUnreadSpotlightPost() {
159        $this->autoRender = false;
160        
161        $teacherId = $this->Auth->user('id');
162        
163        $db = $this->TeacherSpotlightView->getDataSource();
164        $subQuery = $db->buildStatement(
165            array(
166                'fields'         =>     array('TeacherSpotlightView.spotlight_id'),
167                'table'          =>     'teacher_spotlight_view',
168                'alias'          =>     'TeacherSpotlightView',
169                'conditions'     =>     array('TeacherSpotlightView.teacher_id' => $teacherId),
170            ),
171            $this->TeacherSpotlightView
172        );
173    
174        $count = $this->TeacherSpotlight->find('count', array(
175            'joins' => array(
176                array(
177                    'table' => 'teachers',
178                    'alias' => 'Teacher',
179                    'type' => 'INNER',
180                    'conditions' => array('TeacherSpotlight.teacher_id = Teacher.id')
181                )
182            ),
183            'conditions' => array(
184                'NOT' => array('TeacherSpotlight.teacher_id IN (' . $subQuery . ')'),
185                'TeacherSpotlight.status' => 1,
186                'Teacher.spotlight_flag' => 1
187            )
188        ));
189        return json_encode($count);
190    }
191    
192    /**
193     * @api {get} /teacher/api/checkSession checkSession()
194     * @apiName checkSession
195     * @apiGroup API
196     * @apiDescription Checks if the user's session is active.
197     * @apiSampleRequest off
198     * 
199     * @apiSuccess {Number} status Returns 1 if the session is active, 0 if not.
200     * 
201     * @apiErrorExample {json} Success-Response:
202     * {"status": 1}
203     * 
204      * @apiErrorExample {js} Used in: AngularJS
205      * Location: "webroot/js/recruitment/ng/services.js"
206     *
207      * @apiErrorExample {js} Used in: Ajax
208      * Location: "webroot/js/ajaxload.js"
209     */
210    public function checkSession() {
211        $this->autoRender = false;
212        if (!empty($this->Session->read('recruit'))) {
213            return 1;
214        }
215
216        if ($this->Auth->user('id')) {
217            $ts = TeacherStatusLogTable::getTS();
218            $onair = LessonOnairTable::getLessonOnairStatus();
219
220            // add loggers
221            $this->log("[TEACHER_FORCE_DISCONNECTION] teacher status -> " . json_encode($ts), "debug");
222            $this->log("[TEACHER_FORCE_DISCONNECTION] teacher status -> " . json_encode($onair), "debug");
223
224            if ($ts->status == 6 && !$onair) {
225                $this->Session->destroy();
226            }
227        }
228
229        $user_id = $this->Auth->user('id');
230        return empty($user_id) ? 0 : 1;
231    }
232
233    /**
234     * @api {post} /teacher/api/generatePeerID generatePeerID()
235     * @apiName generatePeerID
236     * @apiGroup API
237     * @apiDescription Generates a unique peer ID for the Twilio video chat.
238     * @apiSampleRequest off
239     * 
240     * @apiBody {String} [chatHash] Unique identifier for the chat session.
241     * @apiBody {Number} [userID] ID of the user (default: 0).
242     * @apiBody {String} [userType] Type of user (e.g., "teacher", "student") (default: "teacher").
243     * @apiBody {String} [testConnect] Optional parameter for testing (default: "").
244     * 
245     * @apiSuccess {String} peer_id The generated Twilio peer ID.
246     * @apiSuccess {String} peer_hash The hash corresponding to the generated peer ID.
247     * 
248     * @apiErrorExample {json} Success-Response:
249     *     {
250     *       "peer_id": "unique-peer-id-12345",
251     *       "peer_hash": "hashed-value-67890"
252     *     }
253     * 
254     * @apiErrorExample {js} Used in: Webrtc
255      * Location: "webroot/js/webrtcv2/connect.js"
256      * Location: "webroot/js/webrtcv2/connectEnvModal.js"
257      * Location: "webroot/js/webrtcv2/webrtc.testing.module.js"
258     */
259    public function generatePeerID($chatHash = "", $userID = 0, $userType = "teacher", $testConnect = ""){
260
261        $test = [$chatHash, $userID, $userType, $testConnect];
262        
263        // - load twilio
264        App::uses('myTwilio','Lib');
265
266        // - disallow rendering
267        $this->autoRender = false;
268
269        // - generate twilio peer ID and other meta information
270        $twilio = new myTwilio();
271
272        // - if request came from test_connect page
273        if ($testConnect == "test_connect") {
274            $twilio->ttl = 600;
275        }
276        
277        // - generate ice servers`
278        $twilioIceServers = $twilio->generateIceServers();
279
280        // - generate peer ID
281        $skyWayId = $twilio->generatePeerID($chatHash, $userType);
282
283        // - return hash
284        $skyWayHash = (array)json_decode($twilio->generateHash($skyWayId));
285
286        // - return hash and peer id
287        return json_encode(array('peer_id' => h($skyWayId), 'peer_hash' => h($skyWayHash)));
288    }
289
290    /**
291     * @api {post} /teacher/api/getEndTime getEndTime()
292     * @apiName getEndTime
293     * @apiGroup API
294     * @apiDescription Fetches the remaining time for the lesson to end.
295     * @apiSampleRequest off
296     * 
297     * @apiBody {String} chat_hash Unique identifier for the lesson chat.
298     * 
299     * @apiSuccess {String} min Minutes remaining until the lesson ends.
300     * @apiSuccess {String} sec Seconds remaining until the lesson ends.
301     * @apiSuccess {Number} end_time_stamp Timestamp of the lesson's end time (in seconds since Unix epoch).
302     * @apiSuccess {Number} [end_time_temp] Temporary end time timestamp, defaults to 25 minutes from now if chat_end_time is null.
303     * 
304     * @apiErrorExample {json} Success-Response:
305     *  Custom-End-Time-Response:
306     *     {
307     *       "min": "10",
308     *       "sec": "30",
309     *       "end_time_stamp": 1698129000
310     *     }
311     * 
312     *  Default-End-Time-Response:
313     *     {
314     *       "min": "25",
315     *       "sec": "00",
316     *       "end_time_stamp": null,
317     *       "end_time_temp": 1698130200
318     *     }
319     * 
320     * @apiErrorExample {js} Return type: GET
321     * Returns: die() if method is GET
322     */
323    public function getEndTime(){
324        $this->autoRender = false;
325        $this->layout = false;
326        if ($this->request->is('post')) {
327            $post = $this->request->data;
328            $chat_hash = (isset($post['chat_hash']))?$post['chat_hash']:'';
329            if (empty($chat_hash)) {
330                die('');
331            }
332
333            $onair = $this->LessonOnair->findByChatHash($chat_hash);
334            if (!$onair) {
335                die('');
336            }
337
338            if ($onair['LessonOnair']['chat_end_time'] != null ) {
339                $response = array(
340                    'min' => date('i', strtotime($onair['LessonOnair']['chat_end_time']) - time()),
341                    'sec'=> date('s', strtotime($onair['LessonOnair']['chat_end_time']) - time()),
342                    'end_time_stamp'=> strtotime($onair['LessonOnair']['chat_end_time'])
343                );
344            } else {
345                $response = array(
346                    'min' => '25',
347                    'sec'=> '00',
348                    'end_time_stamp'=> null,
349                    'end_time_temp'=> strtotime('+25 min')
350                );
351            }
352            return json_encode($response);
353        }else{
354            die('');
355        }
356    }
357
358    /**
359     * @api {post} /teacher/api/getNowTime getNowTime()
360     * @apiName getNowTime
361     * @apiGroup API
362     * @apiDescription Fetches the current server time and other lesson details.
363     * @apiSampleRequest off
364     * 
365     * @apiBody {String} chatHash Unique identifier for the lesson chat.
366     * @apiBody {Number} [studentFr=0] Optional flag to trigger face recognition.
367     * 
368     * @apiErrorExample {js} Used in: WebRTC
369      * Location: "webroot/js/recruitment/webrtcv2/event.common.js"
370      * Location: "webroot/js/webrtcv2/event.common.js"
371     */
372    public function getNowTime() {
373        $this->viewClass = 'Json';
374        $setView = array();
375        if ($this->request->is(array('post', 'ajax'))) {
376            $chatHash = $this->request->data['chatHash'];
377            $runFaceRecog = isset($this->request->data['studentFr']) ? $this->request->data['studentFr'] : 0;
378
379            // find onair
380            $this->LessonOnair->clear();
381            $onair = $this->LessonOnair->find('first', array(
382                'fields' => array(
383                    'LessonOnair.id',
384                    'LessonOnair.start_time',
385                    'LessonOnair.end_time',
386                    'LessonOnair.user_id',
387                    'LessonOnair.teacher_id',
388                    'LessonOnair.lesson_type',
389                    'LessonOnair.connect_count',
390                    'LessonOnair.connect_count_active_free',
391                    'LessonOnair.connect_count_active'
392                ),
393                'conditions' => array(
394                    'LessonOnair.chat_hash' => $chatHash
395                ),
396                'recursive' => -1
397            ));
398
399            if (empty($onair['LessonOnair']['end_time'])) {
400                $remainingTime = 0;
401            } else {
402                $endTime = $onair['LessonOnair']['end_time'];
403
404                // get remaining time
405                $nowTimeStamp = strtotime('now');
406                $endTimeStamp = strtotime($endTime);
407                $remainingTime = ($endTimeStamp - $nowTimeStamp);
408            }
409
410            // set view
411            $setView = [
412                'startTime' => ($onair && isset($onair['LessonOnair']['start_time']) && !empty($onair['LessonOnair']['start_time'])) ? $onair['LessonOnair']['start_time'] : null,
413                'nowTime' => date("Y-m-d H:i:s"),
414                'endTime' => ($onair && isset($onair['LessonOnair']['end_time']) && !empty($onair['LessonOnair']['end_time'])) ? $onair['LessonOnair']['end_time'] : null ,
415                'remainingTime' => $remainingTime,
416                'viewers' => isset($onair['LessonOnair']['connect_count']) ? $onair['LessonOnair']['connect_count'] : 0,
417                'active_viewers' => isset($onair['LessonOnair']['connect_count_active']) ? $onair['LessonOnair']['connect_count_active'] : 0                
418            ];
419
420            if ($onair && LessonOnairTable::isSubstituteTeacher($onair['LessonOnair']['teacher_id'], $onair['LessonOnair']['user_id'], $chatHash)) {
421                //include if not exist
422                if (!class_exists('myMemcached')) {
423                    App::uses('myMemcached', 'Lib');
424                }
425                // declare myMemcached
426                $memcached = new myMemcached();
427                $memcached->set(array(
428                    'key' => 'race-condition-lesson-'.$chatHash,
429                    'value' => 1,
430                    'expire' => 10//10 minutes
431                ));
432                $setView['endTime'] = myTools::myDate();
433                $setView['remainingTime'] = 0;
434                $setView['forceEnd'] = true;
435            }
436
437            # START FACE RECOGNITION FUNCTIONS!
438            if ($runFaceRecog && isset($onair['LessonOnair']['user_id'])) {
439                $userId = $onair['LessonOnair']['user_id'];
440                $settingName = Configure::read('setting_names.face_recognition');
441                $options = $this->SettingOption->getOptions($settingName);
442                $userTotalLesson = 0;
443                $setToRun = false;
444
445                #get monthly total lesson
446                $userTotalLesson = $this->LessonOnairsLog->useReplica()->find('count',array(
447                    'conditions' => array(
448                        'LessonOnairsLog.user_id' => $userId,
449                        'LessonOnairsLog.start_time >=' => date('Y-m-01 00:00:00'),
450                        'LessonOnairsLog.start_time <=' => date('Y-m-t 23:59:59'),
451                    )
452                ));
453
454                $this->LessonOnairsLog->openDBReplica();
455                $userTotalSudden = $this->LessonOnairsLog->find('count',array(
456                    'conditions' => array(
457                        'LessonOnairsLog.user_id' => $userId,
458                        'LessonOnairsLog.lesson_type' => 1,
459                        'LessonOnairsLog.start_time BETWEEN ? AND ? ' => array(date('Y-m-d 00:00:00', strtotime('-30 days')), date('Y-m-d 23:59:59', strtotime('-1 days')))
460                    )
461                ));
462                $this->LessonOnairsLog->closeDBReplica();
463                $this->log("[FACERECOG] user_id: {$userId}, userTotalSudden: {$userTotalSudden}, userTotalLesson: {$userTotalSudden}", 'lesson_bookmark');
464
465                $userTotalLesson++;
466                $currentLessonType = !empty($onair['LessonOnair']['lesson_type']) ? $onair['LessonOnair']['lesson_type'] : 1;
467
468                #check if face recognition set to run
469                if (
470                    $options &&
471                    ((isset($options['first_interval']) && trim($options['first_interval']) != "") ||
472                    (isset($options['second_interval']) && trim($options['second_interval']) != "") ||
473                    (isset($options['third_interval']) && trim($options['third_interval']) != ""))
474                ) {
475                    $setToRun = true;
476                }
477
478                if ($options && isset($options['target_lessons']) && $options['target_lessons'] <= $userTotalLesson && $setToRun) {
479                    $frInterval = $options;
480                    unset($frInterval['target_lessons']);
481                    unset($frInterval['frequency']);
482                    $frInterval = array_filter($frInterval);
483                    $frInterval = array_values($frInterval);
484                    $captureFlg = false;
485
486                    # check face auth
487                    $faceRegistered = $this->FaceRegistration->find('first', array(
488                        'fields' => array('chat_hash', 'created'),
489                        'conditions' => array(
490                            'FaceRegistration.acquisition_flg' => 1,
491                            'FaceRegistration.user_id' => $userId
492                        )
493                    ));
494
495                    $this->log("[FACERECOG] user_id: {$userId}, faceRegistered: " . json_encode($faceRegistered), 'lesson_bookmark');
496
497                    # control frequency capture
498                    if ($faceRegistered && isset($options['frequency'])) {
499                        $faceReg = $faceRegistered['FaceRegistration'];
500
501                        //-- disable facerecognation function 
502                        if (
503                            !empty($options['max_lesson_count_exemption']) 
504                            && $userTotalSudden <= $options['max_lesson_count_exemption']
505                        ) {
506                            $captureFlg = false;
507
508                        } elseif ($options['frequency'] == 0) {
509                            $captureFlg = true;
510                        } else {
511                            $countRecog = 1;
512                            $newRegister = false;
513                            $lastRecog = $this->FaceRecognition->find('first', array(
514                                'fields' => array('FaceRecognition.chat_hash', 'LessonOnairsLog.id'),
515                                'joins' => array(
516                                    array(
517                                        'type' => 'LEFT',
518                                        'table' => 'lesson_onairs_logs',
519                                        'alias' => 'LessonOnairsLog',
520                                        'conditions' => 'LessonOnairsLog.chat_hash = FaceRecognition.chat_hash'
521                                    ),
522                                ),
523                                'conditions' => array(
524                                    'FaceRecognition.created >=' => date('Y-m-01 00:00:00'),
525                                    'FaceRecognition.created <=' => date('Y-m-t 23:59:59'),
526                                    'FaceRecognition.created >=' => $faceReg['created'], # make sure after new registration
527                                    'FaceRecognition.user_id' => $userId
528                                ),
529                                'order' => 'FaceRecognition.id DESC'
530                            ));
531                            $this->log('[Check]' . json_encode($lastRecog) , 'debug');
532                            $this->log("[FACERECOG] user_id: {$userId}, lastRecog: " . json_encode($lastRecog), 'lesson_bookmark');
533
534                            if ($lastRecog) {
535                                $lastRecog = $lastRecog['LessonOnairsLog'];
536                                #get ID of last lesson
537                                $countPrev = $this->LessonOnairsLog->find('count', array(
538                                    'conditions' => array(
539                                        'LessonOnairsLog.user_id' => $userId,
540                                        'LessonOnairsLog.id >=' => $lastRecog['id'],
541                                    )
542                                ));
543
544                                #count included in DB to form lesson sequence
545                                $countRecog = $countPrev > 0 ?     ($countPrev + 1) : $countRecog;
546                            } else {
547
548                                if (date('Y-m') == date('Y-m', strtotime($faceReg['created']))) {
549
550                                    $lastRegis = $this->FaceRegistration->find('first', array(
551                                        'fields' => array('LessonOnairsLog.id'),
552                                        'joins' => array(
553                                            array(
554                                                'type' => 'LEFT',
555                                                'table' => 'lesson_onairs_logs',
556                                                'alias' => 'LessonOnairsLog',
557                                                'conditions' => 'LessonOnairsLog.chat_hash = FaceRegistration.chat_hash'
558                                            ),
559                                        ),
560                                        'conditions' => array(
561                                            'FaceRegistration.user_id' => $userId,
562                                            'FaceRegistration.chat_hash' => $faceReg['chat_hash'],
563                                        ),
564                                        'order' => 'FaceRegistration.id DESC'
565                                    ));
566
567                                    $lastRegis = $lastRegis['LessonOnairsLog'];
568                                    $countPrev = $this->LessonOnairsLog->find('count', array(
569                                        'conditions' => array(
570                                            'LessonOnairsLog.user_id' => $userId,
571                                            'LessonOnairsLog.id >=' => $lastRegis['id'],
572                                        )
573                                    ));
574
575                                    #count included in DB to form lesson sequence
576                                    $countRecog = $countPrev > 0 ?     ($countPrev + 1) : $countRecog;
577                                    $newRegister = true;
578                                }
579
580                            }
581
582                            $this->log('[FR] > chat_hash: ' . $chatHash . ', new reg : ' . ($newRegister ? 'true' : 'false') . ', countRecog: ' . $countRecog, 'debug');
583                            $this->log("[FACERECOG] user_id: {$userId}, newRegister: {$newRegister}, countRecog: {$countRecog} ", 'lesson_bookmark');
584                            $seq = (int)$options['frequency'];
585
586                            if ($this->isSequenceOf($countRecog, $seq, $newRegister)) {
587                                $captureFlg = true;
588                            }
589                        }
590
591                        #check match
592                        $sameChatHash = $this->FaceRecognition->find('count', array(
593                            'conditions' => array(
594                                'FaceRecognition.created >=' => date('Y-m-01 00:00:00'),
595                                'FaceRecognition.created <=' => date('Y-m-t 23:59:59'),
596                                'FaceRecognition.user_id' => $userId,
597                                'FaceRecognition.mismatch_flg' => 0,
598                                'FaceRecognition.chat_hash' => $chatHash,
599                            )
600                        ));
601
602                        #if current chathash registration || current mismatch_flg already 0
603                        if ($faceReg['chat_hash'] == $chatHash || $sameChatHash) {
604                            $captureFlg = false;
605                        }
606                    } else {
607                        $captureFlg = true;
608                    }
609
610                    $setView['userTotalLesson'] = $userTotalLesson;
611                    $setView['captureTime'] = $frInterval;
612                    $setView['captureFlg'] = $captureFlg;
613                } else {
614                    $setView['isRunning'] = true;
615                }
616            }
617            # END FACE RECOGNITION FUNCTIONS!
618        }
619
620        $this->set('result', $setView);
621        $this->set('_serialize', 'result');
622    }
623
624    private function isSequenceOf($num, $sequence, $isNewRegistered) {
625        if ($num == 1 && !$isNewRegistered) {
626            return true;
627        } elseif ($num < $sequence) {
628            return false;
629        } else {
630            $ctr = 1;
631            $sequence = $sequence + 1;
632            while($ctr <= $num) {
633                $ctr = $ctr + $sequence;
634                if ($num == $ctr) {
635                    return true;
636                }
637            }
638        }
639        return false;
640    }
641
642    /**
643     * @api {get} /teacher/api/checkForMaintenance checkForMaintenance()
644     * @apiName checkForMaintenance
645     * @apiGroup API
646     * @apiDescription Checks if the application is currently in maintenance mode.
647     * @apiSampleRequest off
648     * 
649     * @apiSuccess {Number} is_maintenance Indicates if the application is currently in maintenance mode (1 for true, 0 for false).
650     * @apiSuccess {String} user_maintenance_time The formatted time of the maintenance in the user's timezone.
651     * @apiSuccess {String} [chat_message] A message indicating the maintenance timing for the user, if applicable.
652     * 
653     * @apiErrorExample {json} Success-Response:
654     *     {
655     *       "is_maintenance": 1,
656     *       "user_maintenance_time": "2:00 AM",
657     *       "chat_message": "--It is 5 minutes before the starting of routine maintenance. We will finish the lesson at 2:00 AM (UTC+5) Asia/Tokyo."
658     *     }
659     * 
660     * @apiErrorExample {js} Used in: WebRTC
661      * Location: "webroot/js/webrtcv2/event.common.js"
662     */
663    public function checkForMaintenance() {
664        $this->autoRender = false;
665        $response = array(
666            'is_maintenance' => 0,
667            'user_maintenance_time' => '2:00 AM'
668        );
669        if (!myTools::checkCompanyIP($_SERVER['REMOTE_ADDR'])) {
670            $conditions = array(
671                'is_active' => 1,
672                'start_date <= DATE_ADD(NOW(), INTERVAL 5 MINUTE)',
673                'end_date >= NOW()'
674            );
675            $maintenanceModel = ClassRegistry::init('Maintenance');
676            $maintenanceModel->openDBReplica();
677            $maintenance = $maintenanceModel->find('first',array(
678                'conditions' => $conditions
679            ));
680            $maintenanceModel->closeDBReplica();
681            if ($maintenance) {
682                $response['is_maintenance'] = 1;
683                $teacher_timezone_id = $this->Teacher->getUpdatedTimezoneID($this->Auth->user('id'));
684                if ($teacher_timezone_id) {
685                    $timezones = $this->Timezone->getFormattedRecruitTimezones();
686                    $userTimeZoneData = $timezones[$teacher_timezone_id];
687                    $datetime = date($maintenance['Maintenance']['start_date']); // maintenance time
688                    $timestamp = strtotime($datetime);
689                    $fromTime = $timestamp + (($userTimeZoneData['jp_time_diff'] / 60) * 60 * 60);
690                    $userMaintenanceTime = date("g:i A", $fromTime);
691                    $uData = $this->Timezone->getTimezoneUserData($teacher_timezone_id);
692                    $response['user_maintenance_time'] = $userMaintenanceTime;
693                    $chatMessage = __d('waiting','--It is 5 minutes before the starting of routine maintenance. We will finish the lesson at ') . $userMaintenanceTime. ' (UTC'.$uData['Timezone']['utc_offset'].') ' .$userTimeZoneData['data-timezone_name'] .'.';
694                    $response['chat_message'] = $chatMessage;
695                }
696            }
697        }
698        echo json_encode($response);
699    }
700
701    /**
702     * @api {get} /teacher/api/checkOngoingMaintenance checkOngoingMaintenance()
703     * @apiName checkOngoingMaintenance
704     * @apiGroup API
705     * @apiDescription Checks if the application is currently in maintenance mode.
706     * @apiSampleRequest off
707     * 
708     * @apiSuccess {Boolean} is_maintenance Indicates if the application is currently in maintenance mode.
709     * @apiSuccess {String} current_url The current URL of the request.
710     * 
711     * @apiErrorExample {json} Success-Response:
712     *     {
713     *       "is_maintenance": false,
714     *       "current_url": "https://example.com/current-page"
715     *     }
716     * 
717      * @apiErrorExample {js} Used in: AngularJS
718      * Location: "webroot/js/ng/app.js"
719
720     * @apiErrorExample {js} Used in: WebRTC
721      * Location: "webroot/js/webrtcv2/event.common.js"
722     */
723    public function checkOngoingMaintenance() {
724        $this->autoRender = false;
725        echo json_encode(
726            array(
727                'is_maintenance' => MaintenanceTable::isMaintenance(),
728                'current_url' => myTools::getUrl()
729            ));
730    }
731
732    /**
733     * @api {post} /teacher/api/getLessonStatus getLessonStatus()
734     * @apiName getLessonStatus
735     * @apiGroup API
736     * @apiDescription Fetches the current status of the lesson.
737     * @apiSampleRequest off
738     * 
739     * @apiBody {String} chat_hash Unique identifier for the chat session.
740     * @apiBody {Object} [teacher_audio_data] Audio data from the teacher.
741     * @apiBody {Object} [student_audio_data] Audio data from the student.
742     *
743      * @apiErrorExample {js} Used in: AngularJS
744      * Location: "webroot/js/ng/app.js"
745     */
746    public function getLessonStatus(){
747        $this->viewClass = 'Json';
748        $response = array();
749
750        // check if has post
751        if ($this->request->is('post')) {
752            $post = $this->request->data;
753            $chat_hash = (isset($post['chat_hash']))?$post['chat_hash']:'';
754
755            // empty chat hash
756            if (!empty($chat_hash)) {
757                $this->LessonOnair->recursive = -1;
758                $onair = $this->LessonOnair->findByChatHash($chat_hash);
759
760                // - check if has onair
761                if ($onair) {
762                    // - if has status
763                    if ($onair['LessonOnair']['status']) {
764                        //check modified date
765                        $connected = ((strtotime($onair['LessonOnair']['modified']) - time()) <= 40) ? 1 : 0;
766                        // set default response
767                        $response = array(
768                            'status' => $onair['LessonOnair']['status'],
769                            'connect_flg' => $onair['LessonOnair']['connect_flg'],
770                            'counselor_flag' => $onair['LessonOnair']['counselor_flag'],
771                            'current_time' => array(
772                                'date_time' => date('Y-m-d H:i:s'),
773                                'y' => date('Y'),
774                                'm' => date('m'),
775                                'd' => date('d'),
776                                'h' => date('H'),
777                                'i' => date('i'),
778                                's' => date('s'),
779                                'time_stamp' => strtotime('now')
780                            ),
781                            'connected' => $connected,
782                            'lesson_current_time' => time(),
783                            'lesson_end_time' => isset($onair['LessonOnair']['end_time']) ? strtotime($onair['LessonOnair']['end_time']) : null,
784                            'is_live' =>  isset($onair['LessonOnair']['live_lesson_flg']) ? $onair['LessonOnair']['live_lesson_flg'] : 0,
785                            'viewers' => isset($onair['LessonOnair']['connect_count']) ? $onair['LessonOnair']['connect_count'] : 0,
786                            'active_viewers' => isset($onair['LessonOnair']['connect_count_active']) ? $onair['LessonOnair']['connect_count_active'] : 0
787                        );
788
789                        $memKey = 'mem_last_4min_or_less_reserve_cancel_' . $onair['LessonOnair']['teacher_id'];
790                        $memcached = new myMemcached();
791                        $memLast4MinOrLessResCancelFlg = $memcached->get($memKey);
792                        $min = date('i');
793                        if (
794                            (($min >= 26 && $min <= 30) || ($min >= 56 && $min <= 59) || $min == 0) &&
795                            ($this->Auth->user('home_flg') && $this->Auth->user('counseling_flg') == 0 ) && !$memLast4MinOrLessResCancelFlg
796                        ) {
797
798                            if ($min >= 26 && $min <= 30) {
799                                $lessonTime = date('Y-m-d H:30:00');
800                            } elseif ($min == 0) {
801                                $lessonTime = date('Y-m-d H:00:00');
802                            } else {
803                                $lessonTime = date('Y-m-d H:00:00', strtotime('+1 hour'));
804                            }
805
806                            $lessonTimeInSec = strtotime($lessonTime);
807                            $cancelledDate = date('Y-m-d H:i:s', strtotime('-5 minutes', $lessonTimeInSec));
808
809                            // NC-4820: check if reservation was cancelled 4 minutes or less before the lesson time
810                            $reservationCancelled = $this->LessonScheduleCancel->find('first', array(
811                                'fields' => array('cancelled_date'),
812                                'conditions' => array(
813                                    'teacher_id' => $onair['LessonOnair']['teacher_id'],
814                                    'lesson_time' => $lessonTime,
815                                    'cancelled_date >=' => $cancelledDate,
816                                    'cancelled_date <' => $lessonTime,
817                                    'status' => 20 // student cancelled less than 1 hour
818                                ),
819                                'recursive' => -1
820                            ));
821
822                            if ($reservationCancelled) {
823                                $response['redirectToNotStandby'] = 1;
824                                $response['chatHash'] = $chat_hash;
825
826                                // set memcached last 4 minute or less reservation cancel flg
827                                if (!$memLast4MinOrLessResCancelFlg) {
828                                    $memcached->set(array(
829                                        'key' => $memKey,
830                                        'value' => true,
831                                        'expire' => 300 // 5 minutes
832                                    ));
833                                }
834                            }
835                        }
836
837                        //NC-7795 set new interval checker to 15 seconds if 5 mins before lesson time
838                        if ((($min >= 26 && $min <= 30) || ($min >= 31 && $min <= 36) || ($min >= 56 && $min <= 59) || ($min >= 00 && $min <= 06)) && $onair['LessonOnair']['status'] == 2) {
839                            $response["setNewIntervalChecker"] = 15000; //15 seconds
840                        } else {
841                             $response["setNewIntervalChecker"] = 30000; //30 seconds
842                        }
843
844                        // if lesson status is reserved, add to teacher status logs
845                        if ($onair['LessonOnair']['status'] == 2) {
846                            $this->addTeacherStatusLog($onair['LessonOnair'],3,$chat_hash);
847                        }
848
849                        // if onair status is lesson, inform teacher
850                        if ($onair['LessonOnair']['status'] == 3) {
851                            $response['lesson_type'] = $onair['LessonOnair']['lesson_type'];
852                            $response['lesson_student_id'] = $onair['LessonOnair']['user_id'];
853
854                            // - if live lesson
855                            if (
856                                !empty($onair['LessonOnair']['lesson_schedule_id']) &&
857                                $onair['LessonOnair']['live_lesson_flg'] == 1
858                            ) {
859                                // - get current live teacher
860                                $this->Teacher->openDBReplica();
861                                $arrTeacherLiveLesson = $this->Teacher->find('first', array(
862                                    'fields' => array(
863                                        'id',
864                                        'live_lesson_sort',
865                                        'live_lesson_flg'
866                                    ),
867                                    'conditions' => array(
868                                        'id' => $onair['LessonOnair']['teacher_id']
869                                    ),
870                                    'recursive' => -1
871                                ));
872                                $this->Teacher->closeDBReplica();
873
874                                // - if has live lesson sort data
875                                if (
876                                    isset($arrTeacherLiveLesson['Teacher']['id']) && 
877                                    isset($arrTeacherLiveLesson['Teacher']['live_lesson_sort']) && 
878                                    $arrTeacherLiveLesson['Teacher']['live_lesson_sort'] != 1
879                                ) {
880                                    $updateSql =  "
881                                        UPDATE 
882                                            `teachers`
883                                        SET 
884                                            `teachers`.`live_lesson_sort` = 1
885                                        WHERE
886                                            id = " . $arrTeacherLiveLesson['Teacher']['id'] . "
887                                    ";
888                                    $this->Teacher->query($updateSql);
889                                }
890                            }
891
892                            // get current user
893                            $this->User->clear();
894                            $this->User->openDBReplica();
895                            $userData = $this->User->findById($onair['LessonOnair']['user_id']);
896                            $this->User->closeDBReplica();
897                            
898                            if ($userData) {
899                                $userTable = new UserTable($userData['User']);
900                                //  NJ-279: check if user's corporate id is not included on the list of disabled file uploading
901                                $response['enable_teacher_file_upload'] = ($userTable->corporate_id != "NULL" && !in_array((int)$userTable->corporate_id, Configure::read("corporate_ids_disabled_file_upload")));
902                            }
903                        }
904
905                        // check if lesson is reservation
906                        if (!empty($onair['LessonOnair']['lesson_schedule_id'])) {
907                            $lessonScheduleId = $onair['LessonOnair']['lesson_schedule_id'];
908                            $this->LessonSchedule->openDBReplica();
909                            $reservation = $this->LessonSchedule->find('first', array(
910                                'fields' => array('LessonSchedule.live_lesson_flg'),
911                                'conditions' => array('LessonSchedule.id' => $lessonScheduleId),
912                                'recursive' => -1
913                            ));
914                            $this->LessonSchedule->closeDBReplica();
915
916                            // update live_lesson_flg
917                            if (!empty($reservation['LessonSchedule']) && $reservation['LessonSchedule']['live_lesson_flg'] == 1 && $onair['LessonOnair']['live_lesson_flg'] != 1) {
918                                $updateSql =  "
919                                    UPDATE 
920                                        `lesson_onairs`
921                                    SET 
922                                        `lesson_onairs`.`live_lesson_flg` = 1
923                                    WHERE
924                                        lesson_schedule_id = " . $lessonScheduleId . "
925                                ";
926                                $this->LessonOnair->query($updateSql);
927                            }
928                        }
929
930                        // Check if teacher audio data is supplied
931                        if (isset($post['teacher_audio_data']) && isset($post['student_audio_data'])) {
932                            $this->saveAudioMemcached($memcached, $post);
933                        }
934                    }
935                    
936                    // - if has connect flag
937                    if ($onair['LessonOnair']['connect_flg'] == 0 && $onair['LessonOnair']['status'] == 2) {
938                        $memKeyOnair = "lesson_onair_connect_flg_" . $chat_hash;
939                        $memcached = new myMemcached();
940                        $checkMemcachedOnair = $memcached->get($memKeyOnair);
941                        
942                        // - check if key was already set
943                        if (!$checkMemcachedOnair) {
944                            // - set key to true
945                            $memcached->set(array(
946                                'key' => $memKeyOnair,
947                                'value' => true,
948                                'expire' => 1500 // 5 minutes
949                            ));
950                            
951                            // - set slack message
952                            $mySlack = new mySlack();
953                            $mySlack->token = "xoxp-392902820692-393103987059-747628176676-c8ea0bf485312302ce3788cc55100b30";
954                            $mySlack->username = "Bad Teacher Connection Debug";
955                            $mySlack->channel = myTools::checkChannel("#bad-teacher-connections", "#bad-teacher-connections-dev");
956                            $mySlack->text = "";
957                            $mySlack->text .= "```";
958                            $mySlack->text .= "chat_hash: " . $chat_hash;
959                            $mySlack->text .= "\nteacher_id: " . $onair['LessonOnair']['teacher_id'];
960                            $mySlack->text .= "\nwait_start_time: " . $onair['LessonOnair']['wait_start_time'];
961                            $mySlack->text .= "```";
962                            
963                            // - send slack message
964                            $test = $mySlack->sendSlack();
965                        }
966                    }
967                    
968                    //- load class
969                    if (!class_exists('myRedis')) {
970                        App::uses('myRedis', 'Lib');
971                    }
972                    $redis = new myRedis();
973
974                    //- refresh lesson onair modified
975                    $redis->set([
976                        'key' => 'lesson_onair_modified_' . $chat_hash,
977                        'value' => time(),
978                        'expire' => 3600 //- expire in 1 hour
979                    ]);
980
981                // check last lesson onairs log
982                } else {
983                    // init memcached
984                    $memcached = new myMemcached();
985
986                    // set mem key
987                    $memKey = $chat_hash.'_student_cancel_in_time';
988
989                    // get student cancel in time chat hash memcached, set on lessonair->studentCancelInTime
990                    $studentCancelInTimeMem = $memcached->get($memKey);
991
992                    // if memcache exist for chat hash
993                    if($studentCancelInTimeMem){
994
995                        // set cookie for home
996                        if( $this->Auth->user('home_flg') && $this->Auth->user('counseling_flg') == 0 ){
997                            // set var for not standby
998                            $teacherStatusCookie = array(
999                                'status_id' => 5,
1000                                'break_id' => 0,
1001                                'break_other' => ''
1002                            );
1003
1004                            // set cookie for not standby
1005                            $this->Cookie->write('TEACHER_STATUS_' . $this->Auth->user('id'), $teacherStatusCookie, false, 1800);
1006                        }
1007
1008                        // set response
1009                        $response['student_cancel_in_time'] = true;
1010
1011                        // delete memcached
1012                        $memcached->delete($memKey);
1013                    }else{
1014
1015                        // get lesson onairs logs
1016                        $lessonLog = $this->LessonOnairsLog->find('first', array(
1017                            'fields' => ['LessonOnairsLog.*', 'LessonOnairsLogsExtend.leave_lesson'],
1018                            'joins' => array(
1019                                array(
1020                                    'table' => 'lesson_onairs_logs_extend',
1021                                    'alias' => 'LessonOnairsLogsExtend',
1022                                    'type' => 'LEFT',
1023                                    'conditions' => array('LessonOnairsLogsExtend.log_id = LessonOnairsLog.id')
1024                                )
1025                            ),
1026                            'conditions' => array(
1027                                'LessonOnairsLog.chat_hash' => $chat_hash
1028                            )
1029                        ));
1030
1031                        //popup browser refresh
1032                        if (!$lessonLog) {
1033                            $this->Cookie->write('dialog_notice_browser_refresh', 1);
1034                        }
1035
1036                    }
1037
1038                    // check if lesson finish exists
1039                    if (isset($lessonLog['LessonOnairsLog']['lesson_finish'])) {
1040                        // if forcefully terminated by lesson
1041                        if (
1042                            $lessonLog['LessonOnairsLog']['lesson_finish'] == 6 || 
1043                            ($lessonLog['LessonOnairsLog']['lesson_finish'] == 1 && 
1044                            $lessonLog['LessonOnairsLogsExtend']['leave_lesson'])
1045                        ) {
1046
1047                            if (isset($lessonLog['LessonOnairsLog']['start_time']) && isset($lessonLog['LessonOnairsLog']['end_time'])) {
1048                                // get  endtime
1049                                $startTime = $lessonLog['LessonOnairsLog']['start_time'];
1050                                $endTime = $lessonLog['LessonOnairsLog']['end_time'];
1051                                $currentTime = time();
1052                                if (strtotime($endTime) > $currentTime) {
1053                                    $endTime = date('Y-m-d H:i:s', $currentTime);
1054                                }
1055
1056                                // check if lesson time is greater than 5 minutes -> prepare Other
1057                                if ((strtotime($endTime) - strtotime($startTime)) > 300) {
1058                                    $this->log("[LESSON_FINISH_STUDENT_FORCE_TERMINATE] setting teacher to other for 3 minutes break -> " . json_encode($lessonLog), 'debug');
1059
1060                                    // set cookie for prepare other
1061                                    $teacherStatusCookie = array(
1062                                        'status_id' => 4,
1063                                        'break_id' => 4,
1064                                        'break_other' => 'after_lesson_other'
1065                                    );
1066                                    $this->log(__METHOD__ . " Setting prepare other cookies! : " . json_encode($teacherStatusCookie), "debug");
1067                                    $this->Cookie->write('TEACHER_STATUS_' . $this->Auth->user('id'), $teacherStatusCookie, false, 1800);
1068
1069
1070                                } else {
1071                                    $this->log("[LESSON_FINISH_STUDENT_FORCE_TERMINATE] lessontime is less than 5 minutes cannot prepare other -> " . json_encode($lessonLog), 'debug');
1072                                }
1073                            } else {
1074                                $this->log("[LESSON_FINISH_STUDENT_FORCE_TERMINATE] teacher cannot prepare other -> " . json_encode($lessonLog), 'debug');
1075                            }
1076                        }
1077
1078                        // NJ-32876
1079                        $memcached = new myMemcached();
1080                        $cachedKey = "midway_lesson_completed_cached_" . $chat_hash;
1081                        $midway_lesson_completed_cached = $memcached->get($cachedKey);
1082
1083                        if (!empty($midway_lesson_completed_cached)) {
1084                            $memcached->delete($cachedKey);
1085                            $response['has_midway_lesson_completed'] = 1;
1086                        }
1087                        // NJ-32876 - end
1088
1089                        // set lesson finish
1090                        $response['lesson_finish'] = $lessonLog['LessonOnairsLog']['lesson_finish'];
1091
1092                        // set lesson current time
1093                        $response['lesson_current_time'] = time();
1094
1095                        // set lesson end time
1096                        $response['lesson_end_time'] = isset($lessonLog['LessonOnairsLog']['end_time']) ? strtotime($lessonLog['LessonOnairsLog']['end_time']) : null;
1097                    }
1098                }
1099            }
1100        }
1101
1102        // set return
1103        $this->set('result', $response);
1104        $this->set('_serialize', 'result');
1105    }
1106
1107    /**
1108    *  // Check if teacher has a recent teacher audio data memcached if none ->add if has ->update
1109    * // get memcache data by key
1110    * @return mixed
1111    */
1112    private function saveAudioMemcached($memcached, $params) {
1113        $chatHash = $params['chat_hash'];
1114        $teacherAudioData = $params['teacher_audio_data'];
1115        $teacherAudioInterval = $params['teacher_audio_interval'];
1116        $studentAudioData = $params['student_audio_data'];
1117        $studentAudioInterval = $params['student_audio_interval'];
1118
1119        $teacherMemkey = 'teacher_audio_data_acquisition_' . $chatHash;
1120        $teacherMemkey2 = 'teacher_audio_interval_' . $chatHash;
1121        $memTeacherAudioData = $memcached->get($teacherMemkey);
1122
1123        //creating or updating memcache (teacher)
1124        if ($chatHash && $teacherAudioData) {
1125            if ($memTeacherAudioData) {
1126                $memcached->set(array(
1127                    'key' => $teacherMemkey,
1128                    'value' => $teacherAudioData,
1129                ));
1130                $memcached->set(array(
1131                    'key' => $teacherMemkey2,
1132                    'value' => $teacherAudioInterval,
1133                ));
1134            } else {
1135                $memcached->set(array(
1136                    'key' => $teacherMemkey,
1137                    'value' => $teacherAudioData,
1138                    'expire' => 1800 // 30 mins
1139                ));
1140                $memcached->set(array(
1141                    'key' => $teacherMemkey2,
1142                    'value' => $teacherAudioInterval,
1143                    'expire' => 1800 // 30 mins
1144                ));
1145            }
1146        }
1147
1148        $studentMemKey = 'student_audio_data_acquisition_' . $chatHash;
1149        $studentMemKey2 = 'student_audio_interval_' . $chatHash;
1150        $memStudentAudioData = $memcached->get($studentMemKey);
1151
1152        //creating or updating memcache (student)
1153        if ($chatHash && $studentAudioData) {
1154            if ($memStudentAudioData) {
1155                $memcached->set(array(
1156                    'key' => $studentMemKey,
1157                    'value' => $studentAudioData,
1158                ));
1159                $memcached->set(array(
1160                    'key' => $studentMemKey2,
1161                    'value' => $studentAudioInterval,
1162                ));
1163            } else {
1164                $memcached->set(array(
1165                    'key' => $studentMemKey,
1166                    'value' => $studentAudioData,
1167                    'expire' => 1800 // 30 mins
1168                ));
1169                $memcached->set(array(
1170                    'key' => $studentMemKey2,
1171                    'value' => $studentAudioInterval,
1172                    'expire' => 1800 // 30 mins
1173                ));
1174            }
1175        }
1176    }
1177
1178    private function addTeacherStatusLog($lessonOnairs,$status,$chatHash) {
1179        $ttl = TeacherStatusLogTable::getTeacherStatusByTeacher($lessonOnairs['teacher_id']);
1180        if ($ttl->status != $status && $ttl->status != 7) {
1181            $data = array(
1182                'teacher_id' => $lessonOnairs['teacher_id'],
1183                'workstation_id' => $ttl->getWorkstationId(),
1184                'status' => $status,
1185                'chat_hash' => $chatHash
1186            );
1187            TeacherStatusLogTable::save($data);
1188            TeacherStatusTable::save($data);
1189        }
1190    }
1191
1192    /**
1193     * @api {post} /teacher/api/getProfile getProfile()
1194     * @apiName getProfile
1195     * @apiGroup API
1196     * @apiDescription Retrieves the profile of a user based on the user ID.
1197     * @apiSampleRequest off
1198     * 
1199     * @apiBody {String} s_user_id The ID of the user whose profile is to be retrieved.
1200     */
1201    
1202    public function getProfile(){
1203        $this->layout = false;
1204        if ($this->request->is('post')) {
1205            $post = $this->request->data;
1206            $userId = (isset($post['s_user_id']))?$post['s_user_id']:'';
1207            if (empty($userId)) {
1208                die('');
1209            }
1210            $this->User->openDBReplica();
1211            $userData = $this->User->findById($userId);
1212            $this->User->closeDBReplica();
1213            if (!$userData) {
1214                die('');
1215            }
1216
1217            $userTable = new UserTable($userData['User']);
1218            $this->set('user',$userTable);
1219
1220            $this->set('enquate1Options', UserTable::getUserEnquate1(json_decode($userTable->enquate1)));
1221            $this->set('enquate2Options', UserTable::getUserEnquate2(json_decode($userTable->enquate2)));
1222            $this->set('enquate3Options', UserTable::getUserEnquate3(json_decode($userTable->enquate3)));
1223            $this->set('enquate4Options', UserTable::getUserEnquate4(json_decode($userTable->enquate4)));
1224
1225        } else {
1226            die();
1227        }
1228    }
1229
1230    /**
1231     * @api {post} /teacher/api/getHeader getHeader()
1232     * @apiName getHeader
1233     * @apiGroup API
1234     * @apiDescription Retrieves lesson header information based on the chat hash, including user details, lesson count, and feedback scores.
1235     * 
1236     * @apiBody {String} chat_hash The chat hash associated with the lesson.
1237     * 
1238     * @apiSampleRequest off
1239     * 
1240     * @apiErrorExample {js} Return Type: View
1241     * Location: "view/Api/get_header.php"
1242     */
1243    public function getHeader() {
1244        $this->layout = false;
1245        if ($this->request->is('post')) {
1246            $post = $this->request->data;
1247            $chat_hash = (isset($post['chat_hash']))?$post['chat_hash']:'';
1248            if (!$chat_hash) {
1249                exit;
1250            }
1251
1252            // onairデータ取得
1253            $onair = $this->LessonUserOnair->findByChatHash($chat_hash);
1254            if (!$onair) {
1255                exit;
1256            }
1257
1258            $user_id = $onair['LessonUserOnair']['user_id'];
1259            $teacher_id = $onair['LessonUserOnair']['teacher_id'];
1260
1261            // 会員情報取得
1262            $user = $this->User->findById($user_id);
1263            if (!$user) {
1264                exit;
1265            }
1266
1267            // 同一講師・生徒でのレッスン回数取得
1268            $this->UsersLessonNote->openDBReplica();
1269            $lesson_cnt = $this->UsersLessonNote->find('count', array('conditions' => array(
1270                'UsersLessonNote.user_id'    => $user_id,
1271                'UsersLessonNote.teacher_id' => $teacher_id,
1272            )));
1273            $this->UsersLessonNote->closeDBReplica();
1274
1275            $enquate6_str='';
1276            if (empty($user['User']['enquate6'])) {
1277                $user['User']['enquate6']=2;
1278            }
1279            $enquate6 = UserTable::getUserEnquate6Eng($user['User']['enquate6']);
1280            $enquate6_str=$enquate6[$user['User']['enquate6']];
1281
1282            $enquate7_str='';
1283            if (empty($user['User']['enquate7'])) {
1284                $user['User']['enquate7']=2;
1285            }
1286            $enquate7 = UserTable::getUserEnquate7Eng($user['User']['enquate7']);
1287            $enquate7_str=$enquate7[$user['User']['enquate7']];
1288
1289            //*NC-4966
1290            $workstation = $this->checkWorkstationSession();
1291
1292            # save to teacher_status log table
1293            $teacherStatusData = array(
1294                'teacher_id' => $this->Auth->user('id'),
1295                'workstation_id' => $workstation['id'],
1296                'status' => 1
1297            );
1298            TeacherStatusLogTable::save($teacherStatusData);
1299            TeacherStatusTable::save($teacherStatusData);
1300
1301            myTools::init_session(true);
1302            $this->Session->write('Teacher.status', '1');
1303            myTools::init_session();
1304
1305            $this->set('teacher', $onair['Teacher']);
1306            $this->set('user',    $user['User']);
1307            $this->set('onair',   $onair['LessonUserOnair']);
1308            $this->set('lesson_cnt', $lesson_cnt);
1309            $this->set('enquate6_str', $enquate6_str);
1310            $this->set('enquate7_str', $enquate7_str);
1311
1312        } else {
1313            exit;
1314        }
1315    }
1316
1317    /**
1318     * @api {post} /teacher/api/getMemo getMemo()
1319     * @apiName getMemo
1320     * @apiGroup API
1321     * @apiDescription Retrieves the teacher's memo for a specific lesson based on the student, teacher, and lesson information.
1322     * 
1323     * @apiBody {Number} t_user_id The teacher's user ID.
1324     * @apiBody {Number} s_user_id The student's user ID.
1325     * @apiBody {String} lesson_id The lesson ID in the format `classId__chapterId`.
1326     * 
1327     * @apiSampleRequest off
1328     */
1329    
1330    public function getMemo() {
1331        $this->autoRender = false;
1332        if ($this->request->is('post')) {
1333            $post = $this->request->data;
1334            $teacherId = (isset($post['t_user_id']))?$post['t_user_id']:'';
1335            $userId = (isset($post['s_user_id']))?$post['s_user_id']:'';
1336            $lessonId = (isset($post['lesson_id']))?$post['lesson_id']:'';
1337            //validating the required field
1338            if (empty($teacherId) || empty($userId) || empty($lessonId)) {
1339                $this->outputMemo(0,'',$lessonId);
1340            }
1341
1342            //check if teacher is exists
1343            $teacherData = $this->Teacher->findById($teacherId);
1344            if (!$teacherData) {
1345                $this->outputMemo(0,'',$lessonId);
1346            }
1347
1348            //check if user is exists
1349            $userData = $this->User->findById($userId);
1350            if (!$userData) {
1351                $this->outputMemo(0,'',$lessonId);
1352            }
1353
1354            list($classId,$chapterId) = explode('__',$lessonId);
1355            $lessonData = $this->LessonText->findByClassIdAndChapterId($classId,$chapterId);
1356            if (!$lessonData) {
1357                $this->outputMemo(0,'',$lessonId);
1358            }
1359
1360            $userclassData = $this->UsersClass->findByUserIdAndClassIdAndChapterIdAndTeacherId($userId,$classId,$chapterId,$teacherId);
1361            if (!$userclassData) {
1362                $this->outputMemo(0,'',$lessonId);
1363            }
1364            $this->outputMemo(1,$userclassData['UsersClass']['teacher_memo'],$lessonId);
1365        } else {
1366            $this->outputMemo();
1367        }
1368    }
1369
1370
1371    private function outputMemo($result=0,$memo='',$lessionId='') {
1372        $data = array(
1373            'result' => $result,
1374            'memo' => $memo,
1375            'lession_id' => $lessionId
1376        );
1377        echo json_encode($data);
1378        exit();
1379    }
1380
1381    /**
1382     * @api {post} /teacher/api/getTransfer getTransfer()
1383     * @apiName getTransfer
1384     * @apiGroup API
1385     * @apiDescription Retrieves the lesson transfer data for a specific student.
1386     * 
1387     * @apiBody {Number} s_user_id The ID of the student whose transfer data is being retrieved.
1388     * 
1389     * @apiSampleRequest off
1390     */
1391    
1392    public function getTransfer() {
1393        $this->layout = false;
1394        if ($this->request->is('post')) {
1395            $post = $this->request->data;
1396            $user_id = (isset($post['s_user_id']))?$post['s_user_id']:'';
1397            if (empty($user_id)) {
1398                exit;
1399            }
1400            $data = $this->LessonTransfer->getDataByUserId($user_id);
1401            $this->set('data', $data);
1402        } else {
1403            exit;
1404        }
1405    }
1406
1407    /**
1408     * @api {post} /teacher/api/setTransfer setTransfer()
1409     * @apiName setTransfer
1410     * @apiGroup API
1411     * @apiDescription handles adding, updating, or deleting a lesson transfer based on the presence of a memo or comment.
1412     * 
1413     * @apiBody {Number} s_user_id The ID of the student.
1414     * @apiBody {Number} t_user_id The ID of the teacher.
1415     * @apiBody {String} [comment] A comment for the lesson transfer.
1416     * 
1417     * @apiSampleRequest off
1418     * 
1419     * @apiSuccess {Boolean} result Indicates if the operation was successful (true) or not (false).
1420     * @apiSuccess {String} [message] A message describing the result of the operation.
1421     * @apiSuccess {Object} [new_data] The new or updated memo data related to the student and teacher.
1422     * 
1423     * @apiErrorExample {json} Success-Response:
1424     * (Insert):
1425     *     {
1426     *       "result": true,
1427     *       "message": "LessonTransfer added successfully",
1428     *       "new_data": { ... }
1429     *     }
1430     * 
1431     * (Update):
1432     *     {
1433     *       "result": true,
1434     *       "message": "LessonTransfer updated successfully",
1435     *       "new_data": { ... }
1436     *     }
1437     * 
1438     * (Delete):
1439     *     {
1440     *       "result": true,
1441     *       "message": "LessonTransfer deleted successfully",
1442     *       "new_data": { ... }
1443     *     }
1444     * 
1445     * (Invalid Request):
1446     *     {
1447     *       "result": false,
1448     *       "message": "Invalid request"
1449     *     }
1450     * 
1451      * @apiErrorExample {js} Used in: AngularJS
1452      * Location: "webroot/js/ng/controller/student_info.js"
1453     */
1454    public function setTransfer() {
1455        $this->autoRender = false;
1456        $data['result'] = false;
1457        $post = $this->request->data;
1458        if ($this->request->is('post')) {
1459            $conditions = array(
1460                'user_id' => $post['s_user_id'],
1461                'teacher_id' => $post['t_user_id']
1462            );
1463
1464            # Get the teachers Memo base on the student
1465            $res = $this->LessonTransfer->getTeacherUserMemo($conditions);
1466
1467            # check if the res is not empty
1468            if(empty($res)){
1469                # check also the comment if not empty
1470                if(!empty($post["comment"])){
1471                    # if not the perform a loop insertion
1472                    $conditions["comment"] = $post["comment"];
1473                    $this->LessonTransfer->add($conditions);
1474
1475                    $data['result'] = true;
1476                }
1477                
1478            }else{
1479                if(!empty($post["comment"])){
1480                    $conditions["comment"] = $post["comment"];
1481                    $this->LessonTransfer->update($conditions);
1482                    $data['message'] = 'LessonTransfer update successfully';
1483                    $data['result'] = true;
1484                }else{
1485                    $this->LessonTransfer->deleteByUserIdAndTeacherId($conditions);
1486                    $data['message'] = 'LessonTransfer deleted successfully';
1487                    $data['result'] = true;
1488                }
1489            }
1490
1491            # newly data transfered
1492            $data['new_data'] = $this->LessonTransfer->getTeacherUserMemo(array(
1493                'user_id'    => $post['s_user_id'],
1494                'teacher_id' => $post['t_user_id']
1495            ));
1496        } else {
1497            $data['message'] = 'Invalid request';
1498        }
1499        return json_encode($data);
1500    }
1501
1502    /**
1503     * @api {post} /teacher/api/updateTransfer updateTransfer()
1504     * @apiName updateTransfer
1505     * @apiGroup API
1506     * @apiDescription Updates the comment for a specific lesson transfer.
1507     * 
1508     * @apiBody {Number} id The ID of the lesson transfer to update.
1509     * @apiBody {String} comment The new comment to update for the lesson transfer.
1510     * 
1511     * @apiSampleRequest off
1512     * 
1513     * @apiSuccess {Boolean} result Indicates if the update was successful.
1514     * @apiSuccess {String} message A message describing the result.
1515     * 
1516     * @apiErrorExample {json} Success-Response:
1517     *     {
1518     *       "result": true,
1519     *       "message": "LessonTransfer add success"
1520     *     }
1521     * 
1522     * (Invalid Parameters):
1523     *     {
1524     *       "result": false,
1525     *       "message": "Invalid parameters"
1526     *     }
1527     * 
1528     * (Invalid Request):
1529     *     {
1530     *       "result": false,
1531     *       "message": "Invalid request"
1532     *     }
1533     * 
1534      * @apiErrorExample {js} Used in: AngularJS
1535      * Location: "webroot/js/ng/controller/student_info.js"
1536     */
1537    public function updateTransfer() {
1538        $this->autoRender = false;
1539        $data['result'] = false;
1540        if ($this->request->is('post')) {
1541            $post = $this->request->data;
1542            $id = (isset($post['id']))?$post['id']:'';
1543            $comment = (isset($post['comment']))?$post['comment']:'';
1544            if (empty($id) || empty($comment)) {
1545                $data['message'] = 'Invalid parameters';
1546            }else{
1547                $this->LessonTransfer->read('comment', $id);
1548                if($this->LessonTransfer->saveField('comment', $comment)) {
1549                    $data['message'] = 'LessonTransfer add success';
1550                    $data['result'] = true;
1551                }
1552            }
1553        } else {
1554            $data['message'] = 'Invalid request';
1555        }
1556        echo json_encode($data);
1557        exit();
1558    }
1559
1560    /**
1561     * @api {post} /teacher/api/deleteTransfer deleteTransfer()
1562     * @apiName deleteTransfer
1563     * @apiGroup API
1564     * @apiDescription sets the status of a LessonTransfer record to 0 (deleting the transfer).
1565     * 
1566     * @apiBody {Number} id The ID of the LessonTransfer to be deleted.
1567     * 
1568     * @apiSampleRequest off
1569     * 
1570     * @apiSuccess {Boolean} result Indicates if the operation was successful (true) or not (false).
1571     * @apiSuccess {String} message A message describing the result of the operation.
1572     * 
1573     * @apiErrorExample {json} Success-Response:
1574     *     {
1575     *       "result": true,
1576     *       "message": "LessonTransfer delete success"
1577     *     }
1578     * 
1579     *  (Invalid Parameters):
1580     *     {
1581     *       "result": false,
1582     *       "message": "Invalid parameters"
1583     *     }
1584     * 
1585     *  (Invalid Request):
1586     *     {
1587     *       "result": false,
1588     *       "message": "Invalid request"
1589     *     }
1590     */
1591    public function deleteTransfer() {
1592        $this->autoRender = false;
1593        $data['result'] = false;
1594        if ($this->request->is('post')) {
1595            $post = $this->request->data;
1596            $id = (isset($post['id']))?$post['id']:'';
1597            if (empty($id)) {
1598                $data['message'] = 'Invalid parameters';
1599            }else{
1600                $this->LessonTransfer->read('status', $id);
1601                if($this->LessonTransfer->saveField('status', 0)) {
1602                    $data['message'] = 'LessonTransfer delete success';
1603                    $data['result'] = true;
1604                }
1605                $data['message'] = 'LessonTransfer delete success';
1606                $data['result'] = true;
1607            }
1608        } else {
1609            $data['message'] = 'Invalid request';
1610        }
1611        echo json_encode($data);
1612        exit();
1613    }
1614
1615    /**
1616     * @api {get} /teacher/api/triggerLessonNoteValidation triggerLessonNoteValidation()
1617     * @apiName triggerLessonNoteValidation
1618     * @apiGroup API
1619     * @apiDescription validates a lesson note by updating the `valid_flg` field of the record associated with the provided `chatHash`.
1620     * 
1621     * @apiBody {String} chatHash The chat hash of the lesson note to be validated.
1622     * 
1623     * @apiSampleRequest off
1624     * 
1625     * @apiSuccess {Number} success Indicates if the operation was successful (1 for success, 0 for failure).
1626     * @apiSuccess {Number} errors The number of errors (0 for success, 1 for failure).
1627     * @apiSuccess {String} message A message describing the result of the operation.
1628     * 
1629     * @apiErrorExample {json} Success-Response:
1630     *     {
1631     *       "success": 1,
1632     *       "errors": 0,
1633     *       "message": "updated"
1634     *     }
1635     * 
1636     * @apiErrorExample {json} Error-Response:
1637     *     {
1638     *       "success": 0,
1639     *       "errors": 1,
1640     *       "message": "error occured"
1641     *     }
1642     */
1643    
1644    public function triggerLessonNoteValidation() {
1645        $this->autoRender = false;
1646        if ($this->request->is(array('get', 'ajax'))) {
1647            $chatHash = $this->request->query['chatHash'];
1648
1649            if (isset($chatHash) && !empty($chatHash)) {
1650                $data = $this->UsersLessonNote->find('first', array(
1651                    'fields' => array(
1652                        'UsersLessonNote.id',
1653                        'UsersLessonNote.chat_hash'
1654                    ),
1655                    'conditions' => array(
1656                        'UsersLessonNote.chat_hash' => $chatHash
1657                    )
1658                ));
1659
1660                if ($data) {
1661                    $this->UsersLessonNote->read(null, $data['UsersLessonNote']['id']);
1662                    $this->UsersLessonNote->saveField('valid_flg', '1');
1663                    return json_encode(array('success' => 1, 'errors' => 0, 'message' => 'updated'));
1664                }
1665                return json_encode(array('success' => 0, 'errors' => 1, 'message' => 'error occured'));
1666            }
1667        }
1668    }
1669
1670    /**
1671     * @api {post} /teacher/api/message_action saveTeacherLearnings()
1672     * @apiName saveTeacherLearnings
1673     * @apiGroup API
1674     * @apiDescription allows teachers to save their lesson learnings, including various textbook-related data and progress. It handles different types of textbooks, including Callan textbooks.
1675     * 
1676     * @apiHeader {String} Content-Type application/json
1677     * @apiHeader {String} teachers_api_token Teacher's API token for authentication.
1678     * 
1679     * @apiBody {String} lessonId ID of the lesson.
1680     * @apiBody {Number} textbook_category_type The type of textbook (e.g., Callan, Non-Callan).
1681     * @apiBody {String} [topic] Lesson's topic.
1682     * @apiBody {String} [pages] Pages covered in the lesson.
1683     * @apiBody {String} [comment] Comments regarding the lesson.
1684     * @apiBody {String} [voc1] Vocabulary 1.
1685     * @apiBody {String} [voc2] Vocabulary 2.
1686     * @apiBody {String} [voc3] Vocabulary 3.
1687     * @apiBody {Number} [callan_level_check] Indicates if Callan level check is performed.
1688     * @apiBody {Number} [callanStageNum] Callan stage number.
1689     * @apiBody {String} [callanHeadWord] Headword for Callan.
1690     * @apiBody {Number} [callanRevisionMode] Callan revision mode.
1691     * @apiBody {Number} execute Indicates if the action is 'save' or 'send'.
1692     * 
1693     * @apiSuccess {String} modified The date and time of the last modification.
1694     * @apiSuccess {Number} lesson_memo_disp_flg Indicates if the lesson memo is displayed.
1695     * @apiSuccess {Object} lesson_memo The lesson memo data.
1696     * @apiSuccess {String} lesson_memo_sent_time The date and time the lesson memo was sent.
1697     * @apiSuccess {String} callan_reading_note Reading note for Callan.
1698     * @apiSuccess {Number} lesson_memo_read_flg Indicates if the lesson memo is read.
1699     * 
1700     * 
1701     * @apiErrorExample {json} Success-Response:
1702     * {
1703     *   "modified": "2024-10-24 14:00:00",
1704     *   "lesson_memo_disp_flg": 1,
1705     *   "lesson_memo": {...},
1706     *   "lesson_memo_sent_time": "2024-10-24 14:05:00",
1707     *   "callan_reading_note": "Sample reading note",
1708     *   "lesson_memo_read_flg": 0
1709     * }
1710     * 
1711      * @apiErrorExample {js} Used in: AngularJS
1712      * Location: "webroot/js/ng/controller/student_info.js"
1713      * Location: "webroot/js/ng/controller/message.js"
1714     *
1715     * @apiSampleRequest off
1716     */
1717    /*
1718    * NC-587 save teacher learnings to be shown by the user or
1719    * to save as drafts only.
1720    * created due to changes in the said issue.
1721    * the idea of saving is due to the adaptation of the old functionality
1722    */
1723    public function saveTeacherLearnings() {
1724        $this->autoRender = false;
1725        $read = false;
1726        
1727        if ($this->request->is('ajax')) {
1728
1729            $teacherId = $this->Auth->User('id');
1730            $postRaw = array_map('trim', $this->request->data);
1731            $post = self::sanitizeInputs( array( "data" => $postRaw ) );
1732            $data = array();
1733            $return = null;
1734            $json = array();
1735            $callanLastStage = null;
1736
1737        } else if ($this->request->is('post')){
1738            $this->response->type('json');
1739            $requestData = json_decode($this->request->input(), true);
1740            $token = isset($requestData['teachers_api_token']) && !empty($requestData['teachers_api_token']) ? $requestData['teachers_api_token'] : null;
1741
1742            //validations
1743            if (!$token) {
1744                $response['error']['id'] = Configure::read('error.teachers_api_token_can_not_be_empty');
1745                $response['error']['message'] = __('teachers_api_token can not be empty');
1746                return json_encode($response);
1747            }
1748            if(!isset($requestData['lessonId']) && !$requestData['lessonId']){
1749                $response['error']['id'] = Configure::read('error.invalid_lesson_id');
1750                $response['error']['message'] = __('lessonId can not be empty');
1751                return json_encode($response);
1752            }
1753            if(!isset($requestData['textbook_category_type']) && empty($requestData['textbook_category_type'])){
1754                $response['error']['id'] = Configure::read('error.invalid_textbookCategoryType');
1755                $response['error']['message'] = __('textbook_category_type can not be empty');
1756                return json_encode($response);
1757            }
1758            if(!isset($requestData['action']) && !$requestData['action']){
1759                $response['error']['id'] = Configure::read('error.message_action');
1760                $response['error']['message'] = __('action was not specified');
1761                return json_encode($response);
1762            }
1763            
1764            $teacher = $this->Teacher->getFromToken($token);
1765
1766            if(!$teacher){
1767                $response['error']['id'] = Configure::read('error.missing_teacher');
1768                $response['error']['message'] = __('invalid teacher');
1769                return json_encode($response);
1770            }
1771
1772            $teacherId = $teacher['id'];
1773            $post = self::sanitizeInputs( array( "data" => $requestData ) );
1774            $data = array();
1775            $return = null;
1776            $json = array();
1777            $callanLastStage = null;        
1778        }
1779
1780        if($post){
1781
1782            # check if the textbook category type is not set
1783            if (!isset($post['textbook_category_type']) || !isset($post['lessonId'])) {
1784                return 'false';
1785            }
1786
1787            // - check if comment is more than 1000
1788            if (
1789                isset($post['comment']) && 
1790                !empty($post['comment']) && 
1791                mb_strlen($post['comment']) > 1000 &&
1792                $this->Auth->User('counseling_flg') == 0
1793            ) {
1794                return 'false';
1795            }
1796
1797            # if the category type is not callan
1798            if ($post['textbook_category_type'] != 2 && $post['textbook_category_type'] != 5 && $post['textbook_category_type'] != 6 && $post['textbook_category_type'] != 7) {
1799
1800                if ($post['execute'] == 0) { /* bypass required fields if action is save */
1801                    /* do nothing */
1802                } else
1803                // required fields
1804                if (
1805                    (
1806                        empty($post['topic']) ||
1807                        empty($post['comment']) ||
1808                        ( empty($post['voc1']) && empty($post['voc2']) && empty($post['voc3']) )
1809                    )
1810                ) {
1811                    if($token && $teacher){
1812                        $response['error']['message'] = __('Today\'s Lesson, Page, Vocabulary( Input at least one field ) and Comment fields are required');
1813                        return json_encode($response);
1814                    }
1815                    return 'false';
1816                }
1817
1818                # set vocabulary
1819                $vocabulary = array(
1820                    strip_tags($post['voc1']),
1821                    strip_tags($post['voc2']),
1822                    strip_tags($post['voc3'])
1823                );
1824
1825                # set phrases
1826                $phrases = array(
1827                    strip_tags($post['phrase1']),
1828                    strip_tags($post['phrase2'])
1829                );
1830
1831                # set json value
1832                $json = array(
1833                    'message_5' => array(strip_tags($post['topic_user'])),
1834                    'message_6' => $vocabulary,
1835                    'message_7' => $phrases,
1836                    'message_8' => strip_tags($post['pronunciation']),
1837                    'message_9' => strip_tags($post['grammar']),
1838                    'message_10' => strip_tags($post['comment']),
1839                    'message_11' => !empty($post['pages']) ? strip_tags($post['pages']) : null,
1840                    'message_12' => strip_tags($post['textbook_type']),
1841                    'english_topic' => array(strip_tags($post['topic'])),
1842                );
1843
1844            # if the category type is callan
1845            } else if ($post['textbook_category_type'] == 2 && $post['callan_level_check'] == 0) {
1846                # verify if params exist
1847                $post['callan_reading_note'] = isset($post['callan_reading_note']) ? $post['callan_reading_note'] : '';
1848                $post['callanTextbookType'] = isset($post['callanTextbookType']) ? $post['callanTextbookType'] : '';
1849                $post['callanReadingStageNum'] = isset($post['callanReadingStageNum']) ? $post['callanReadingStageNum'] : '';
1850                $post['callanReadParagraphNum'] = isset($post['callanReadParagraphNum']) ? $post['callanReadParagraphNum'] : '';
1851                $post['callanReadLastHeadWord'] = isset($post['callanReadLastHeadWord']) ? $post['callanReadLastHeadWord'] : '';
1852                $post['callanStageNum'] = isset($post['callanStageNum']) ? $post['callanStageNum'] : '';
1853                $post['callanParagraphNum'] = isset($post['callanParagraphNum']) ? $post['callanParagraphNum'] : '';
1854                $post['callanHeadWord'] = isset($post['callanHeadWord']) ? $post['callanHeadWord'] : '';
1855                $post['callanRevisionMode'] = isset($post['callanRevisionMode']) ? $post['callanRevisionMode'] : '';
1856                $post['nextTeacherNote'] = isset($post['nextTeacherNote']) ? $post['nextTeacherNote'] : '';
1857                $post['callanReadingCheckbox'] = isset($post['callanReadingCheckbox']) ? $post['callanReadingCheckbox'] : '';
1858
1859                # Set User's Highest Callan Stage
1860                $callanLastStage = strip_tags($post['callanStageNum']);
1861                # question array
1862                $questionAnswer = array(
1863                    strip_tags($post['callanStageNum']),
1864                    strip_tags($post['callanParagraphNum']),
1865                    strip_tags($post['callanHeadWord']),
1866                    strip_tags($post['callanRevisionMode'])
1867                );
1868
1869                # reading array
1870                $reading = array(
1871                    strip_tags($post['callanReadingStageNum']),
1872                    strip_tags($post['callanReadParagraphNum']),
1873                    strip_tags($post['callanReadLastHeadWord'])
1874                );
1875
1876                # json data for callan
1877                $json = array(
1878                    'message_13' => strip_tags($post['callanTextbookType']),
1879                    'message_14' => strip_tags($post['callanChapter']),
1880                    'message_15' => $questionAnswer,
1881                    'message_16' => $reading,
1882                    'callan_reading_checkbox' => $post['callanReadingCheckbox']
1883                );
1884
1885                if ($post['execute'] == 0) { /* bypass required fields if action is save */
1886                    /* do nothing */
1887                } else
1888                # check if the important parameters are empty
1889                if (
1890                    empty($post['callanStageNum']) ||
1891                    empty($post['callanParagraphNum']) ||
1892                    empty($post['callanHeadWord']) ||
1893                    empty($post['callanRevisionMode'])
1894                ) {
1895                    return 'false';
1896                }
1897
1898            # if the category type is callan
1899            } else if ($post['textbook_category_type'] == 5 ) {
1900                # verify if params exist
1901                $post['callanTextbookType'] = isset($post['callanTextbookType']) ? $post['callanTextbookType'] : '';
1902                $post['callanReadParagraphNum'] = isset($post['callanReadParagraphNum']) ? $post['callanReadParagraphNum'] : '';
1903                $post['callanReadLastHeadWord'] = isset($post['callanReadLastHeadWord']) ? $post['callanReadLastHeadWord'] : '';
1904                $post['callanParagraphNum'] = isset($post['callanParagraphNum']) ? $post['callanParagraphNum'] : '';
1905                $post['callanHeadWord'] = isset($post['callanHeadWord']) ? $post['callanHeadWord'] : '';
1906                $post['callanRevisionMode'] = isset($post['callanRevisionMode']) ? $post['callanRevisionMode'] : '';
1907
1908                # question array
1909                $questionAnswer = array(
1910                    strip_tags($post['callanParagraphNum']),
1911                    strip_tags($post['callanHeadWord']),
1912                    strip_tags($post['callanRevisionMode'])
1913                );
1914
1915                # reading array
1916                $reading = array(
1917                    strip_tags($post['callanReadParagraphNum']),
1918                    strip_tags($post['callanReadLastHeadWord'])
1919                );
1920
1921                # json data for callan
1922                $json = array(
1923                    'message_13' => strip_tags($post['callanTextbookType']),
1924                    'message_14' => strip_tags($post['callanChapter']),
1925                    'message_15' => $questionAnswer,
1926                    'message_16' => $reading
1927                );
1928                if ($post['execute'] == 0) { /* bypass required fields if action is save */
1929                    /* do nothing */
1930                } else
1931                # check if the important parameters are empty
1932                if (
1933                    empty($post['callanParagraphNum']) ||
1934                    empty($post['callanHeadWord']) ||
1935                    empty($post['callanRevisionMode'])
1936                ) {
1937                    return 'false';
1938                }
1939            # if the category type is callan
1940            } else if ($post['textbook_category_type'] == 6 ) {
1941                # verify if params exist
1942                $post['callanTextbookType'] = isset($post['callanTextbookType']) ? $post['callanTextbookType'] : '';
1943                $post['callanReadingStageNum'] = isset($post['callanReadingStageNum']) ? $post['callanReadingStageNum'] : '';
1944                $post['callanReadParagraphNum'] = isset($post['callanReadParagraphNum']) ? $post['callanReadParagraphNum'] : '';
1945                $post['callanReadLastHeadWord'] = isset($post['callanReadLastHeadWord']) ? $post['callanReadLastHeadWord'] : '';
1946                $post['callanStageNum'] = isset($post['callanStageNum']) ? $post['callanStageNum'] : '';
1947                $post['callanParagraphNum'] = isset($post['callanParagraphNum']) ? $post['callanParagraphNum'] : '';
1948                $post['callanHeadWord'] = isset($post['callanHeadWord']) ? $post['callanHeadWord'] : '';
1949                $post['callanRevisionMode'] = isset($post['callanRevisionMode']) ? $post['callanRevisionMode'] : '';
1950
1951                # question array
1952                $questionAnswer = array(
1953                    strip_tags($post['callanStageNum']),
1954                    strip_tags($post['callanParagraphNum']),
1955                    strip_tags($post['callanHeadWord']),
1956                    strip_tags($post['callanRevisionMode'])
1957                );
1958
1959                # reading array
1960                $reading = array(
1961                    strip_tags($post['callanReadingStageNum']),
1962                    strip_tags($post['callanReadParagraphNum']),
1963                    strip_tags($post['callanReadLastHeadWord'])
1964                );
1965
1966                # json data for callan
1967                $json = array(
1968                    'message_13' => strip_tags($post['callanTextbookType']),
1969                    'message_14' => strip_tags($post['callanChapter']),
1970                    'message_15' => $questionAnswer,
1971                    'message_16' => $reading
1972                );
1973
1974                if ($post['execute'] == 0) { /* bypass required fields if action is save */
1975                    /* do nothing */
1976                } else
1977                # check if the important parameters are empty
1978                if (
1979                    empty($post['callanStageNum']) ||
1980                    empty($post['callanParagraphNum']) ||
1981                    empty($post['callanHeadWord']) ||
1982                    empty($post['callanRevisionMode'])
1983                ) {
1984                    return 'false';
1985                }
1986
1987            # if the category type is callan and for level checking
1988            } else if ($post['textbook_category_type'] == 2 && $post['callan_level_check'] == 1) {
1989                # verify if the params exists
1990                $post['callanLevelCheckTextbookType'] = isset($post['callanLevelCheckTextbookType']) ? $post['callanLevelCheckTextbookType'] : '';
1991                $post['callanLevelCheckChapter'] = isset($post['callanLevelCheckChapter']) ? $post['callanLevelCheckChapter'] : '';
1992                $post['callanLevelCheckStageNum'] = isset($post['callanLevelCheckStageNum']) ? $post['callanLevelCheckStageNum'] : '';
1993
1994                # Set User's Highest Callan Stage
1995                $callanLastStage = $post['callanLevelCheckStageNum'];
1996                # json data for callan
1997                $json = array(
1998                    'message_13' => strip_tags($post['callanLevelCheckTextbookType']),
1999                    'message_14' => strip_tags($post['callanLevelCheckChapter']),
2000                    'message_17' => strip_tags($post['callanLevelCheckStageNum']),
2001                );
2002
2003                # check if the teacher set it to send
2004                if (
2005                    isset($post['execute']) &&
2006                    $post['execute'] == 1 &&
2007                    isset($post['userId'])
2008                ) {
2009                    # set default status to not finished
2010                    $setUserCallanCheckStatus = Configure::read('callan_entry_level.not_finished');
2011
2012                    # check if level check test has finished
2013                    if ($post['callanLevelCheckStageNum'] != 'Not Finished') {
2014                        $setUserCallanCheckStatus = Configure::read('callan_entry_level.finished');
2015                    }
2016
2017                    # set user's callan level check to finished
2018                    $this->User->updateCallanLevelCheck(array(
2019                        'user_id' => $post['userId'],
2020                        'callan_level_check' => $setUserCallanCheckStatus
2021                    ));
2022                }
2023
2024                if ($post['execute'] == 0) { /* bypass required fields if action is save */
2025                    /* do nothing */
2026                } else
2027                # check if important parameter is present
2028                if (empty($post['callanLevelCheckChapter'])) {
2029                    return 'false';
2030                }
2031
2032            } else if ($post['textbook_category_type'] == 7) {
2033
2034                if ($post['execute'] == 0) { /* bypass required fields if action is save */
2035                    /* do nothing */
2036                } else
2037                # check if the important parameters are empty
2038                if (
2039                    empty($post['comment'])
2040                ) {
2041                    if($token && $teacher){
2042                        $response['error']['message'] = __('Comment is a required field!');
2043                        return json_encode($response);
2044                    } else{
2045                        return 'false';
2046                    }
2047                }
2048
2049                # set json value
2050                $json = array(
2051                    'message_10' => array(strip_tags($post['comment'] ?? "")),
2052                    'message_19' => array(strip_tags($post["recommended_textbook"] ?? "")),
2053                    'message_20' => array(strip_tags($post['category'] ?? "")),
2054                );
2055
2056            }
2057
2058            $data = array(
2059                'modified' => date('Y-m-d H:i:s'),
2060                'lesson_memo_disp_flg' => $post['execute'],
2061                'lesson_memo' => json_encode($json),
2062                'lesson_memo_sent_time' => ( $post['execute'] == 1 )? date('Y-m-d H:i:s') : null , // update sent time
2063                'callan_reading_note' => isset($post['callan_reading_note']) ? $post['callan_reading_note'] : null,
2064                'lesson_memo_read_flg' => 0
2065            );
2066
2067            if(isset($post['lessonId']) && $post['lessonId']){
2068                $this->Teacher->openDBReplica();
2069                $teacherInfo = $this->Teacher->find('first', array(
2070                    'fields' => array(
2071                        'Teacher.id',
2072                        'Teacher.name',
2073                        'Teacher.home_flg'
2074                    ),
2075                    'joins' => array(
2076                        array(
2077                            'table' => 'lesson_onairs_logs',
2078                            'alias' => 'LessonOnairsLog',
2079                            'type' => 'LEFT',
2080                            'conditions' => array(
2081                                'LessonOnairsLog.teacher_id = Teacher.id'
2082                            )
2083                        )
2084                    ),
2085                    'conditions' => array(
2086                        'LessonOnairsLog.id' => $post['lessonId']
2087                    ),
2088                    'recursive' => -1
2089                ));
2090                $this->Teacher->closeDBReplica();
2091            }
2092
2093            //check prohibited words
2094            if(is_array($teacherInfo) && !empty($teacherInfo['Teacher'])){
2095                if ($post['execute'] == 1) {
2096                    $modifiedComment = $this->ProhibitedWord->checkProhibitedWords($post['comment']);
2097                    if ($modifiedComment) {
2098                        $this->postSlack(array(
2099                            'comment' => $modifiedComment,
2100                            'userId' => $post['userId'],
2101                            'userName' => $post['userName'],
2102                            'teacherId' => $teacherInfo['Teacher']['id'],
2103                            'teacherName' => $teacherInfo['Teacher']['name'],
2104                            'homeFlag' => $teacherInfo['Teacher']['home_flg'],
2105                            'lesson_number' => isset($this->request->data['lessonNumber']) ? $this->request->data['lessonNumber'] : "",
2106                            'lesson_id' => $post['lessonId'],
2107                            'is_lesson_onair' => $post['isLessonOnair']
2108                        ));
2109                    }
2110                }
2111            }
2112
2113            //
2114            $officeIncentiveData = array();
2115
2116            // check if successfully saved
2117            try {
2118
2119                if ($post['action'] == 'update') {
2120                    unset($data['lesson_memo_sent_time']); // remain memo_sent_time
2121                    $this->SendEditMessageSlack($post['lessonId'], $teacherId, $data);
2122                }
2123
2124                if ($post['isLessonOnair']) {
2125                    # update the user lesson onair
2126                    $this->LessonOnair->clear();
2127                    $read = $this->LessonOnair->read(array('id', 'user_id', 'lesson_memo_sent_time'), $post['lessonId']);
2128                    if ($read) {
2129                        // set initial lesson data for incentive checking
2130                        $officeIncentiveData = $read['LessonOnair'] ?? array();
2131
2132                        $this->LessonOnair->clear();
2133                        $this->LessonOnair->id = $post['lessonId'];
2134                        $this->LessonOnair->set($data);
2135                        $save = $this->LessonOnair->save();
2136                    }
2137                } else {
2138                    # update the user lesson on air log
2139                    $this->LessonOnairsLog->clear();
2140                    $read = $this->LessonOnairsLog->read(array('id', 'user_id', 'lesson_memo_sent_time'), $post['lessonId']);
2141                    if ($read) {
2142                        // set initial lesson data for incentive checking
2143                        $officeIncentiveData = $read['LessonOnairsLog'] ?? array();
2144
2145                        $this->LessonOnairsLog->clear();
2146                        $this->LessonOnairsLog->id = $post['lessonId'];
2147                        $this->LessonOnairsLog->set($data);
2148                        $save = $this->LessonOnairsLog->save();
2149                    }
2150                }
2151
2152                // NC-3564 : save reading progress
2153                if ( $save && $post['textbook_category_type'] == 2 && $post['callan_level_check'] == 0 && !$post['callanReadingCheckbox'] ) {
2154
2155                    $progressData = array(
2156                        "user_id" => $post['userId'],
2157                        "stage" => $post['callanReadingStageNum'],
2158                        "paragraph_number" => $post['callanReadParagraphNum'],
2159                        "content" => $post['callanReadLastHeadWord'],
2160                        "next_teacher_note" => $post['nextTeacherNote']
2161                    );
2162                    UsersTextbookProgressTable::saveProgress($progressData);
2163                }
2164                /* Update User Callan information*/
2165                if ($callanLastStage && $save) {
2166                    $callanStageparams = array(
2167                        "user_id" => $post['userId'],
2168                        "lesson_id" => $post['lessonId'],
2169                        "stage_num" => $callanLastStage,
2170                        "isLessonOnairTable" => $post['isLessonOnair']
2171                    );
2172                    ClassRegistry::init('UsersCallanInformation')->lessonOnAirUpdateCallanStages($callanStageparams);
2173                }
2174
2175            } catch (Exception $e) {
2176                $this->log(__METHOD__ . " [Exception Error] " .  $e->getMessage(), "debug");
2177                return 'errExecute';
2178            }
2179
2180            // if first sent message, not test user and office teacher, give message incentives
2181            if(
2182                $save &&
2183                (isset($post['action']) && $post['action'] === 'send') && 
2184                ($officeIncentiveData && empty($officeIncentiveData['lesson_memo_sent_time'])) && 
2185                strpos(strtoupper($post['userName']), '%%%TEST%%%') === false
2186            ){
2187                // get teacher data
2188                $teacherData = $this->Teacher->getTeacherInfo($teacherId);
2189
2190                // init teacher data
2191                $teacherData = $teacherData['Teacher'];
2192
2193                // check if office teacher
2194                if(isset($teacherData['home_flg']) && !$teacherData['home_flg']){
2195
2196                    // set incentive params
2197                    $incentiveParams = array(
2198                        'teacher_id' => $teacherId,
2199                        'incentive_date' => $data['lesson_memo_sent_time'] ?? $data['modified'] ?? null,
2200                        'incentive_point' => 1
2201                    );
2202
2203                    // init message incentive process
2204                    $this->OfficeMessageIncentive->setTeacherIncentive($incentiveParams);
2205                }
2206
2207            }
2208
2209            # check if saving was successful
2210            if (!$save) {
2211                if($token && $teacher){
2212                    $response['result'] = 0;
2213                    $response['message'] = 'failed';
2214                }
2215                return 'false';
2216            }
2217
2218
2219
2220            # return '1' to signify that the saving was successful
2221            if($token && $teacher){
2222                $response['result'] = 1;
2223                if($post['execute'] == '1'){
2224                    if($post['action'] === "send"){
2225                        $response['message'] = 'sent';
2226                    }else{
2227                        $response['message'] = 'updated';
2228                    }
2229                }else{
2230                    $response['message'] = 'saved';
2231                }
2232                return json_encode($response);
2233            }else {
2234                return 1;
2235            }
2236            
2237        }
2238    }
2239
2240    /**
2241    * NC-2190: send changes message prev -> new message to slack
2242    */
2243    private function SendEditMessageSlack($lessonId, $teacherId, $new) {
2244        // send the changes the slack
2245        $lesson_message = new LessonMessageController();
2246        $condition = "WHERE Lesson.id = ".$lessonId;
2247        $prev = $lesson_message->getLessonOnAirsList(1, 0, null, false, $condition, $teacherId);
2248        $prevDetail = new LessonOnairTable($prev[0]['Lesson']);
2249        $arr = (array)json_decode($prevDetail->lesson_memo);
2250        $prevMessage = "";
2251
2252        // get previous(old) message
2253        if (array_key_exists('message_5', $arr)) {
2254            $rearr = myTools::getLessonNoteMessage2($arr, 'en', null, true, "\n");
2255            $prevMessage = myTools::getLessNoteMsg($rearr, "\n");
2256        } else {
2257            $prevMessage = myTools::getLessonNoteMessage(array('lessonMessage' => json_encode($arr)), "\n");
2258        }
2259
2260        $newDetail = new LessonOnairTable($new);
2261        $arr = (array)json_decode($newDetail->lesson_memo);
2262        $newMessage = "";
2263
2264        // get new(updated) message
2265        if (array_key_exists('message_5', $arr)) {
2266            $rearr = myTools::getLessonNoteMessage2($arr, 'en', null, true, "\n");
2267            $newMessage = myTools::getLessNoteMsg($rearr, "\n");
2268        } else {
2269            $newMessage = myTools::getLessonNoteMessage(array('lessonMessage' => json_encode($arr)), "\n");
2270        }
2271
2272        // remove unnecessary <br/>
2273        $prevMessage = preg_replace('/<[^>]*>/', '', $prevMessage);
2274        $newMessage = preg_replace('/<[^>]*>/', '', $newMessage);
2275
2276        // get teacher detail
2277        $teacher = $this->Teacher->find('first', array(
2278            'fields' => array('Teacher.name'),
2279            'conditions' => array(
2280                'Teacher.id' => $prevDetail->teacher_id
2281            ),
2282            'recursive' => -1
2283            )
2284        );
2285
2286        $this->mySlack->sendSlackMessageChanged(array(
2287            'teacher_id' => $prevDetail->teacher_id,
2288            'teacher_name' => $teacher['Teacher']['name'],
2289            'user_id' => $prev[0]['User']['id'],
2290            'user_name' =>$prev[0]['User']['nickname'],
2291            'prevMessage' => $prevMessage,
2292            'newMessage' => $newMessage
2293        ));
2294    }
2295
2296    
2297    /*
2298    * array required val is ok
2299    * method created for checking teacher learnings required values
2300    */
2301    private function arrReqValIsOk($array = array()) {
2302        $errCnt = 0;
2303        foreach ($array as $key => $value) {
2304            if (is_array($value)) {
2305                foreach ($value as $k =>$v) {
2306                    if (empty($v)) {
2307                        $errCnt++;
2308                    }
2309                }
2310            } else {
2311                if (empty($value)) {
2312                    $errCnt++;
2313                }
2314            }
2315        }
2316
2317        if ($errCnt > 0) {
2318            return false;
2319        } else {
2320            return true;
2321        }
2322    }
2323
2324    /**
2325     * @api {post} /teacher/api/saveTeacherMemo saveTeacherMemo()
2326     * @apiName saveTeacherMemo
2327     * @apiGroup API
2328     * @apiDescription saves the teacher memo for a student.
2329     * 
2330     * @apiBody {String} id The ID of the lesson.
2331     * @apiBody {String} memo_status The status of the memo (1 for visible, 0 for hidden).
2332     * @apiBody {String} comment The memo to save.
2333     * @apiBody {String} required The required fields.
2334     * 
2335     * @apiSuccess {Number} result The result of the operation (1 for success, 0 for failure).
2336     * 
2337     * @apiSampleRequest off
2338     */
2339    
2340    public function saveTeacherMemo() {
2341        $this->autoRender = false;
2342        if ($this->request->is('ajax')) {
2343            $response = array(
2344                'status' => 'failed' // set failed as default value
2345            );
2346            $data = $this->request->data;
2347
2348            // 必要なデータがない
2349            if(empty($data["id"])) { return; }
2350            if(!isset($data["memo_status"])) { return; }
2351
2352            $lesson_memo_disp_flg = ($data["memo_status"]==1) ? 1 : 0;
2353
2354            foreach ($data["comment"] as $key  => $value) {
2355
2356                foreach ($value as $detail_key => $detail_value) {
2357                    // 必須項目のメッセージがない(但し、SAVEの場合を除く)
2358                    if( $lesson_memo_disp_flg==1 && !empty($data["required"][$key][$detail_key]) && (int)$data["required"][$key][$detail_key] === 1 && empty($detail_value) ) {
2359                        return;
2360                    }
2361                    // escape
2362//                    $data["comment"][$key][$detail_key] = htmlspecialchars($detail_value);
2363                }
2364            }
2365
2366            $this->UsersLessonNote->set(array(
2367                'id' => $data['id'],
2368                'lesson_memo' => json_encode($data['comment']),
2369                'lesson_memo_disp_flg' => $lesson_memo_disp_flg,
2370            ));
2371            // Success!
2372            if ($this->UsersLessonNote->save()) {
2373                return 1;
2374            }
2375        }
2376    }
2377
2378    /**
2379     * @api {post} /teacher/api/setMemo setMemo()
2380     * @apiName setMemo
2381     * @apiGroup API
2382     * @apiDescription sets the teacher memo for a student.
2383     * 
2384     * @apiBody {String} t_user_id The ID of the teacher.
2385     * @apiBody {String} s_user_id The ID of the student.
2386     * @apiBody {String} lesson_id The ID of the lesson.
2387     * @apiBody {String} memo The memo to set.
2388     * 
2389     * @apiSuccess {Number} result The result of the operation (1 for success, 0 for failure).
2390     * 
2391      * @apiErrorExample {js} Used in: AngularJS
2392      * Location: "webroot/js/ng/controller/message.js"
2393      * Location: "webroot/js/ng/controller/student_info.js"
2394      * Location: "webroot/js/recruitment/ng/controllers/userMemo.js"
2395     *
2396     * @apiSampleRequest off
2397     */
2398    public function setMemo(){
2399        $this->autoRender = false;
2400        if ($this->request->is('post')) {
2401            $post = $this->request->data;
2402            $teacherId = (isset($post['t_user_id']))?$post['t_user_id']:'';
2403            $userId = (isset($post['s_user_id']))?$post['s_user_id']:'';
2404            $lessonId = (isset($post['lesson_id']))?$post['lesson_id']:'';
2405            $memo = (isset($post['memo']))?$post['memo']:'';
2406            //validating the required field
2407
2408            if (empty($teacherId) || empty($userId) || empty($lessonId) || empty($memo)) {
2409                $this->outputSetMemo();
2410            }
2411
2412            //check if teacher is exists
2413            $teacherData = $this->Teacher->findById($teacherId);
2414            if (!$teacherData) {
2415                $this->outputSetMemo();
2416            }
2417
2418            //check if user is exists
2419            $userData = $this->User->findById($userId);
2420            if (!$userData) {
2421                $this->outputSetMemo();
2422            }
2423
2424            list($classId,$chapterId) = explode('__',$lessonId);
2425            $lessonData = $this->LessonText->findByClassIdAndChapterId($classId,$chapterId);
2426            if (!$lessonData) {
2427                $this->outputSetMemo();
2428            }
2429
2430            $userclassData = $this->UsersClass->findByUserIdAndClassIdAndChapterIdAndTeacherId($userId,$classId,$chapterId,$teacherId);
2431            if (!$userclassData) {
2432                $this->outputSetMemo();
2433            } else {
2434                $this->UsersClass->read(null,$userId);
2435                $this->UsersClass->set(array(
2436                    'teacher_memo' => $memo
2437                ));
2438                $this->UsersClass->save();
2439                $this->outputSetMemo(1);
2440            }
2441        }else{
2442            $this->outputSetMemo();
2443        }
2444    }
2445
2446    private function outputSetMemo($result=0){
2447        $data = array(
2448            'result' => $result,
2449        );
2450        echo json_encode($data);
2451        exit();
2452    }
2453
2454    /**
2455     * @api {get} /teacher/api/getAnnounceCount getAnnounceCount()
2456     * @apiName getAnnounceCount
2457     * @apiGroup API
2458     * @apiDescription returns the number of announcements for a teacher.
2459     * 
2460     * @apiSuccess {Number} count The number of announcements.
2461     * 
2462      * @apiErrorExample {js} Used in: AngularJS
2463      * Location: "webroot/js/ng/app.js"
2464      * Location: "webroot/js/ng/home.js"
2465      * Location: "webroot/js/ng/controller/home.js"
2466      * Location: "webroot/js/ng/controller/reservation.js"
2467      * Location: "webroot/js/recruitment/ng/controllers/services.js"
2468     *
2469     * @apiSampleRequest off
2470     */
2471    public function getAnnounceCount() {
2472        $this->autoRender = false;
2473        $curTime = date("Y-m-d H:i:00", time());
2474
2475        // get announcement counts
2476        $this->LessonSchedule->openDBReplica();
2477        $cnt = $this->LessonSchedule->find('count', array(
2478            'conditions' => array(
2479                'LessonSchedule.lesson_time >='=> $curTime,
2480                'LessonSchedule.teacher_id' => $this->Auth->user('id'),
2481                'LessonSchedule.teacher_check_flg' => 1,
2482                'LessonSchedule.status' => 1
2483            ),
2484        ));
2485        $this->LessonSchedule->closeDBReplica();
2486
2487        // return total count
2488        return $cnt;
2489    }
2490
2491    /**
2492     * @api {post} /teacher/api/getRequestCount getRequestCount()
2493     * @apiName getRequestCount
2494     * @apiGroup API
2495     * @apiDescription returns the number of lesson requests for a teacher.
2496     * 
2497     * @apiSuccess {Number} count The number of lesson requests.
2498     * 
2499      * @apiErrorExample {js} Used in: AngularJS
2500     * Location: "webroot/js/ng/app.js"
2501      * Location: "webroot/js/ng/home.js"
2502     *
2503     * @apiSampleRequest off
2504     */
2505    public function getRequestCount() {
2506        $this->autoRender = false;
2507        $curTime = date("Y-m-d H:i:00", time());
2508
2509        // get announcement counts
2510        $this->LessonSchedule->openDBReplica();
2511        $cnt = $this->LessonSchedule->find('count', array(
2512            'conditions' => array(
2513                'LessonSchedule.lesson_time >='=> $curTime,
2514                'LessonSchedule.teacher_id' => $this->Auth->user('id'),
2515                'LessonSchedule.teacher_check_flg' => 1,
2516                'LessonSchedule.status' => Configure::read('lesson_schedule.status.lesson_request')
2517            ),
2518        ));
2519        $this->LessonSchedule->closeDBReplica();
2520
2521        // return total count
2522        return $cnt;
2523    }
2524
2525    /**
2526     * @api {get} /teacher/api/getNextReservationTime getNextReservationTime()
2527     * @apiName getNextReservationTime
2528     * @apiGroup API
2529     * @apiDescription returns the time remaining until the next lesson reservation for a teacher.
2530     * 
2531     * @apiBody {Boolean} [homeBase] Indicates if the request is from home base.
2532     * @apiBody {String} [teacherId] The ID of the teacher (optional, defaults to the authenticated user).
2533     * 
2534     * @apiSuccess {Number} reserve_time Time remaining until the next lesson reservation in seconds.
2535     * @apiSuccess {Number} shift_meal_break Time remaining until the next meal break slot in seconds.
2536     * @apiSuccess {Number} meal_break_duration Duration of the current meal break slot in minutes.
2537     * @apiSuccess {Boolean} teacher_is_meal_break Indicates if the teacher is currently on a meal break.
2538     * @apiSuccess {Boolean} teacher_force_end_meal_break Indicates if the teacher is forced to end the meal break.
2539     * 
2540      * @apiErrorExample {js} Used in: AngularJS
2541      * Location: "webroot/js/ng/services.js"
2542      * Location: "webroot/js/ng/home.js"
2543      * Location: "webroot/js/ng/controller/home.js"
2544      * Location: "webroot/js/recruitment/ng/services.js"
2545     * @apiSampleRequest off
2546     */
2547    // NC-5554 : @modified add next meal break slot
2548    // called on interval, when the teacher is not standby.
2549    public function getNextReservationTime() {
2550        $this->autoRender = false;
2551
2552        $teacherId = isset($this->request->data["teacherId"]) ? $this->request->data["teacherId"] : $this->Auth->user('id');
2553        $homeFlag = isset($this->request->data["homeBase"]) && !$this->request->data["homeBase"] ? 0 : $this->Auth->user('home_flg');
2554
2555        $curTime = date("Y-m-d H:i:00", time());
2556        $return = array();
2557        $mb = null;
2558        $mealBreakStart = 0;
2559        $return['meal_break_duration'] = -1;
2560        $return["teacher_is_meal_break"] = 0;
2561        $return["teacher_force_end_meal_break"] = 0;
2562
2563        $row = $this->LessonSchedule->find('first', array(
2564            'fields' => array('LessonSchedule.lesson_time'),
2565            'conditions' => array(
2566                'LessonSchedule.status' => 1,
2567                'LessonSchedule.lesson_time >= ' => $curTime,
2568                'LessonSchedule.teacher_id' => $teacherId,
2569            ),
2570            'order' => array('LessonSchedule.lesson_time' => 'asc')
2571        ));
2572
2573        if (!$homeFlag) {
2574            if (date('i') >= 30) {
2575                $lessonTime = date("Y-m-d H:30:00", time());
2576            } else {
2577                $lessonTime = date("Y-m-d H:00:00", time());
2578            }
2579
2580            $mb = $this->ShiftWorkMealBreak->find('first', array(
2581                'fields' => array('ShiftWorkMealBreak.lesson_time'),
2582                'conditions' => array(
2583                    'ShiftWorkMealBreak.lesson_time' => $lessonTime,
2584                    'ShiftWorkMealBreak.teacher_id' => $teacherId
2585                ),
2586                'order' => array('ShiftWorkMealBreak.lesson_time' => 'asc')
2587            ));
2588            $currenTS = $this->TeacherStatus->find('first', array(
2589                'fields' => array('created'),
2590                'conditions' => array(
2591                    'status' => 4, // Break
2592                    'remarks1' => 1, // Lunch Break
2593                    'teacher_id' => $teacherId
2594                )
2595            ));
2596            // if no meal break but teacher status is still on meal break
2597            if(!$mb && !empty($currenTS['TeacherStatus']['created'])) {
2598                // NC-6644 force end meal break
2599                $return["teacher_force_end_meal_break"] = 1;
2600                $return["teacher_is_meal_break"] = 1;
2601            }
2602            if (!empty($currenTS['TeacherStatus']['created'])) {
2603
2604                $start = strtotime(date('Y-m-d H:00:00'));
2605                $end = strtotime(date('Y-m-d H:i:00'));
2606                if (date('i') >= 30) {
2607                    $start = strtotime(date('Y-m-d H:30:00'));
2608                }
2609
2610                $return['meal_break_duration'] = round(abs($end - $start) / 60);
2611                $return["teacher_is_meal_break"] = 1;
2612            }
2613        }
2614
2615        $return['reserve_time'] = '';
2616        $return['shift_meal_break'] = '';
2617
2618        if (!empty($row['LessonSchedule']['lesson_time'])) {
2619            $return['reserve_time'] = (strtotime($row['LessonSchedule']['lesson_time']) - time());
2620        }
2621        if (!empty($mb['ShiftWorkMealBreak']['lesson_time'])) {
2622            // get the next slot meal break
2623            $return['shift_meal_break'] = (strtotime($mb['ShiftWorkMealBreak']['lesson_time']) - time());
2624        }
2625
2626        return json_encode($return);
2627    }
2628
2629    /**
2630     * @api {get} /teacher/api/checkIfMealBreakTime checkIfMealBreakTime()
2631     * @apiName checkIfMealBreakTime
2632     * @apiGroup API
2633     * @apiDescription checks if the current time is scheduled for a meal break for a teacher.
2634     * 
2635     * @apiBody {Boolean} [homeBase] Indicates if the request is from home base.
2636     * @apiBody {String} [teacherId] The ID of the teacher (optional, defaults to the authenticated user).
2637     * 
2638     * @apiSuccess {Boolean} hasMealBreak Indicates if there is a meal break.
2639     * @apiSuccess {Number} timeRemaining Time remaining until the next meal break in seconds.
2640     * 
2641     * @apiErrorExample {json} Success-Response:
2642     * {
2643     *     "timeRemaining": 0,
2644     *     "hasMealBreak": true
2645     * }
2646     * 
2647      * @apiErrorExample {js} Used in: AngularJS
2648      * Location: "webroot/js/ng/app.js"
2649     *
2650     * @apiSampleRequest off
2651     */
2652    /**
2653    * NC-6314: Called when the teacher is on standby
2654    * Check and set time remaining to 0 if current slot was scheduled for mealbreak
2655    * Set time remaining to next slot if current slot was not scheduled for mealbreak
2656    */
2657    public function checkIfMealBreakTime () {
2658        $this->autoRender = false;
2659        $return = array('hasMealBreak' => false);
2660        $homeFlag = isset($this->request->data["homeBase"]) && !$this->request->data["homeBase"] ? 0 : $this->Auth->user('home_flg');
2661        if (!$homeFlag) {
2662            $teacherId = isset($this->request->data["teacherId"]) ? $this->request->data["teacherId"] : $this->Auth->user('id');
2663            $currentTime = time();
2664            $mealBreakBefore = false;
2665
2666            if (date('i') >= 30) {
2667                $lessonTime = date("Y-m-d H:30:00", $currentTime);
2668            } else {
2669                $lessonTime = date("Y-m-d H:00:00", $currentTime);
2670            }
2671
2672            // get last teacher status
2673            $data = $this->TeacherStatusLog->find('first', array(
2674                'fields' => array('status', 'remarks1'),
2675                'conditions' => array(
2676                    'teacher_id' => $teacherId,
2677                    'status <>' => 2,
2678                    'created >=' => $lessonTime,
2679                    'created <=' => date('Y-m-d H:i:s')
2680                ),
2681                'order' => 'created DESC'
2682            ));
2683
2684            // set mealbreak before to true is previous status is mealbreak
2685            if ($data) {
2686                if ($data['TeacherStatusLog']['status'] == 4 && $data['TeacherStatusLog']['remarks1'] == 1) {
2687                    $mealBreakBefore = true;
2688                }
2689            }
2690
2691            // check if currrent time was scheduled for mealbreak
2692            $count = $this->ShiftWorkMealBreak->find('count', array(
2693                'conditions' => array(
2694                    'lesson_time' => $lessonTime,
2695                    'teacher_id' => $teacherId
2696                )
2697            ));
2698
2699            if ($count && !$mealBreakBefore) {
2700                return json_encode(array(
2701                    'timeRemaining' => 0,
2702                    'hasMealBreak' => true
2703                ));
2704            }
2705
2706            return json_encode(array(
2707                'timeRemaining' => strtotime('+30 minutes ' . $lessonTime) - $currentTime,
2708                'hasMealBreak' => true
2709            ));
2710        }
2711
2712        return json_encode($return);
2713    }
2714
2715
2716    /**
2717     * @api {post} /teacher/api/setLessonMemo setLessonMemo()
2718     * @apiName setLessonMemo
2719     * @apiGroup API
2720     * @apiDescription sets a memo for a lesson by saving the user ID, teacher ID, and comment.
2721     * 
2722     * @apiBody {String} s_user_id The ID of the student user.
2723     * @apiBody {String} t_user_id The ID of the teacher user.
2724     * @apiBody {String} comment The comment to be added to the lesson memo.
2725     * 
2726     * @apiSuccess {String} 1 Memo successfully added.
2727     * @apiError -1 Missing required parameters (user_id, teacher_id, or comment).
2728     * @apiError -2 Invalid request method (not POST).
2729     * 
2730     * @apiSampleRequest off
2731     */
2732    public function setLessonMemo() {
2733        $this->autoRender = false;
2734        if ($this->request->is('post')) {
2735            $post = $this->request->data;
2736            $user_id    = (isset($post['s_user_id']))?$post['s_user_id']:'';
2737            $teacher_id = (isset($post['t_user_id']))?$post['t_user_id']:'';
2738            $comment    = (isset($post['comment']))?$post['comment']:'';
2739            if (empty($user_id) || empty($teacher_id) || empty($comment)) {
2740                echo "-1";
2741                exit;
2742            }
2743            $res = $this->LessonMemo->add(array(
2744                'user_id'    => $user_id,
2745                'teacher_id' => $teacher_id,
2746                'comment'    => $comment,
2747            ));
2748            echo $res;
2749            exit;
2750        } else {
2751                echo "-2";
2752            exit;
2753        }
2754    }
2755
2756    //used only in this controller
2757    public function createTeacherStatusLog($teacher_id, $workstation_id, $status_id, $teacher_current_status = null, $tcs_created = null, $break_id = null, $break_others= null) {
2758        $break_others = (is_null($break_others)) ? "TEACHER_ACTION" : $break_others;
2759        $data = array(
2760            'teacher_id' => $teacher_id,
2761            'workstation_id' => $workstation_id,
2762            'status' => $status_id,
2763            'remarks1' => $break_id,
2764            'remarks2' => $break_others,
2765            'homeFlag' => $this->Auth->User('home_flg'),
2766            'rankId' => $this->Auth->User('rank_coin_id'),
2767            'teacherCurrentStatus' => $teacher_current_status,
2768            'tcsCreated' => $tcs_created
2769        );
2770
2771        if(isset($data['status']) && $data['status'] == 2) {
2772            $this->log(__METHOD__ . '[TEACHER STATUS STANDBY] teacher_id -> ' . json_encode($teacher_id), 'error');
2773        }
2774
2775        TeacherStatusLogTable::save($data);
2776        return TeacherStatusTable::save($data);
2777    }
2778
2779    /**
2780     * @api {get} /teacher/api/getUnviewedNotice getUnviewedNotice()
2781     * @apiName getUnviewedNotice
2782     * @apiGroup API
2783     * @apiDescription returns the number of unviewed notices for a teacher.
2784     * @apiSampleRequest off
2785     * 
2786      * @apiErrorExample {js} Used in: AngularJS
2787      * Location: "webroot/js/ng/controller/home.js"
2788      * Location: "webroot/js/ng/controller/notices.js"
2789      * Location: "webroot/js/ng/app.js"
2790      * Location: "webroot/js/ng/home.js"
2791      * Location: "webroot/js/recruitment/ng/services.js"
2792     */
2793
2794    public function getUnviewedNotice() {
2795        $this->autoRender = false;
2796        return PostTable::countUnviewedPost();
2797    }
2798
2799    /**
2800     * @api {get} /teacher/api/getUnviewedRule getUnviewedRule()
2801     * @apiName getUnviewedRule
2802     * @apiGroup API
2803     * @apiDescription returns the number of unviewed rules for a teacher.
2804     * @apiSampleRequest off
2805     * 
2806     * @apiErrorExample {js} Used in: AngularJS
2807      * Location: "webroot/js/ng/controller/home.js"
2808      * Location: "webroot/js/ng/controller/rules.js"
2809      * Location: "webroot/js/ng/app.js"
2810      * Location: "webroot/js/ng/home.js"
2811      * Location: "webroot/js/recruitment/ng/services.js"
2812     */
2813    public function getUnviewedRule() {
2814        $this->autoRender = false;
2815        return RuleTable::countUnviewedRule();
2816    }
2817
2818    /**
2819     * @api {post} /teacher/api/askHelp askHelp()
2820     * @apiName askHelp
2821     * @apiGroup API
2822     * @apiDescription sets the teacher status to help requested.
2823     * @apiSampleRequest off
2824     * 
2825     * @apiBody {Boolean} askHelp Indicates if help is requested.
2826     *
2827     * @apiErrorExample {json} Success-Response:
2828     *     {
2829     *       "flag": true
2830     *     }
2831     *
2832     * @apiErrorExample {json} Error-Response:
2833     *     {
2834     *       "flag": false
2835     *     }
2836     * 
2837      * @apiErrorExample {js} Used in: AngularJS
2838     * Location: "webroot/js/ng/controller/header.js"
2839     */
2840    public function askHelp() {
2841        $this->autoRender = false;
2842        if ($this->request->is('post')) {
2843            //*NC-4966
2844            $workstation = $this->checkWorkstationSession();
2845
2846            $askHelp = $this->request->data['askHelp'];
2847            $statChoices = TeacherStatusLogTable::statChoices();
2848            $wsId = $workstation['id'];
2849            $teacherId = $this->Auth->User('id');
2850            $statusLog = TeacherStatusLogTable::getTeacherStatusByTeacher($teacherId);
2851
2852            // NC-2706
2853            $teacherCurrentStatus = isset($statusLog->status) ? $statusLog->status : null;
2854            $tcsCreated = isset($statusLog->created) ? $statusLog->created : null;
2855
2856            $flag = true;
2857            if ($statusLog->status == $statChoices['HELP']) {
2858                $flag = false;
2859            } else {
2860                if ($askHelp == 'true') {
2861                    $this->createTeacherStatusLog($teacherId, $wsId, $statChoices['HELP'], $teacherCurrentStatus, $tcsCreated);
2862                } else {
2863                    $flag = false;
2864                }
2865            }
2866            return json_encode($flag);
2867        }
2868    }
2869
2870    /**
2871     * @api {get} /teacher/api/getBreaks getBreaks()
2872     * @apiName getBreaks
2873     * @apiGroup API
2874     * @apiDescription checks if the teacher can take a break.
2875     * @apiSampleRequest off
2876     * 
2877     * @apiSuccess {Boolean} break Indicates if the teacher can take a break.
2878     * @apiSuccess {Boolean} mealBreak Indicates if the teacher can take a meal break.
2879     * @apiSuccess {Boolean} redirectNotStandby Indicates if the teacher is not on standby.
2880     * @apiSuccess {Boolean} hasOpenedSlot Indicates if the teacher has an opened slot.
2881     * 
2882     * @apiErrorExample {json} Success-Response:
2883     *     {
2884     *       "break": true,
2885     *       "mealBreak": true,
2886     *       "redirectNotStandby": false,
2887     *       "hasOpenedSlot": true
2888     *     }
2889     *
2890     * @apiErrorExample {json} Error-Response:
2891     *     {
2892     *       "break": false,
2893     *       "mealBreak": false,
2894     *       "redirectNotStandby": false,
2895     *       "hasOpenedSlot": false
2896     *     }
2897     * 
2898     * @apiErrorExample {js} Used in: AngularJS
2899     * Location: "webroot/js/ng/controller/home.js"
2900     * Location: "webroot/js/ng/app.js"
2901     * Location: "webroot/js/ng/home.js"
2902      * Location: "webroot/js/recruitment/ng/services.js"
2903     */
2904    public function getBreaks() {
2905        $this->autoRender = false;
2906        $teacher_id = $this->Auth->user('id');
2907        $breakLimit = null;
2908        $mealBreak = false;
2909        $break = false;
2910        $teacherOnBreaks = '';
2911        $hasReservation = false;
2912        //check if stealth teacher.
2913        $this->Teacher->openDBReplica();
2914        $this->Teacher->recursive = -1;
2915        $stealth = $this->Teacher->find('count', array(
2916            'conditions' => array('id' => $teacher_id, 'stealth_flg' => 1)
2917        ));
2918        $this->Teacher->closeDBReplica();
2919
2920        $minutes_now = date('i', strtotime('now'));
2921
2922        $time_start = date('H:30');
2923        $time_end = date('H:00', strtotime('+1 hour'));
2924
2925        if ($minutes_now >= 0 && $minutes_now < 30) {
2926            $time_start = date('H:00');
2927            $time_end = date('H:30');
2928        }
2929        if ($stealth) {
2930            //check if teacher can break.
2931            $break = true;
2932            $mealBreak = true;
2933        } else {
2934            // check if booked
2935
2936            // get reservation time
2937            $currentReservationTime = false;
2938            if (date("i") >= 30 && date("i") <= 56) {
2939                $currentReservationTime = date('Y-m-d H:30:00');
2940            } elseif (date("i") >= 0 && date("i") <= 26) {
2941                $currentReservationTime = date('Y-m-d H:00:00');
2942            }
2943
2944            // check if $currentReservationTime has value
2945            if ($currentReservationTime) {
2946                $hasReservation = $this->LessonSchedule->find('count', array(
2947                        'conditions' => array(
2948                                'LessonSchedule.teacher_id' => $teacher_id,
2949                                'LessonSchedule.status' => 1,
2950                                'LessonSchedule.lesson_time' => $currentReservationTime
2951                        )
2952                ));
2953            }
2954
2955            if (!$hasReservation) {
2956                $this->BreakLimit->openDBReplica();
2957                $breakLimitData = $this->BreakLimit->find('first', array(
2958                        'conditions' => array(
2959                                "TIME_FORMAT(start_time, '%H:%i') = " => $time_start,
2960                                "TIME_FORMAT(end_time, '%H:%i') = " => $time_end
2961                        ),
2962                        'fields' => array('BreakLimit.break', 'BreakLimit.meal', 'BreakLimit.start_time', 'BreakLimit.end_time', 'BreakLimit.total')
2963                ));
2964                $this->BreakLimit->closeDBReplica();
2965                if (!empty($breakLimitData)) {
2966                    $startDate = date('Y-m-d '.$breakLimitData['BreakLimit']['start_time']);
2967                    $endDate = date('Y-m-d '.$breakLimitData['BreakLimit']['end_time']);
2968                    $breakLimit = $breakLimitData['BreakLimit']['break'];
2969                    $mealBreakLimit = $breakLimitData['BreakLimit']['meal'];
2970                    $breakLimitTotal =  $breakLimitData['BreakLimit']['total'];
2971
2972                    $this->TeacherStatus->openDBReplica();
2973                    $teacherBreaks= $this->TeacherStatus->getDataSource();
2974                    $data = $teacherBreaks->fetchAll("
2975                            SELECT
2976                                SUM(CASE WHEN `ts`.`remarks1` = 1 THEN 1 ELSE 0 END) as `lunch_break`,
2977                                SUM(CASE WHEN `ts`.`remarks1` = 2 THEN 1 ELSE 0 END) as `break`
2978                            FROM `teachers` AS `t`
2979                            INNER JOIN `teacher_status` AS `ts`
2980                            ON (`t`.id = `ts`.`teacher_id` AND `t`.stealth_flg = 0)
2981                            WHERE `ts`.`status` = 4 AND `ts`.`remarks1` IN (1 , 2)
2982                                AND `ts`.`created` BETWEEN '" . $startDate . "'
2983                                AND '" . $endDate . "'"
2984                            );
2985                    $this->TeacherStatus->closeDBReplica();
2986                    $data = $data[0][0];
2987                    $teachersOnBreak = is_null($data['break']) ? 0 : $data['break'];
2988                    $teachersOnMealBreak = is_null($data['lunch_break']) ? 0 : $data['lunch_break'];
2989
2990                    // check teacher meal break limit
2991                    if ($mealBreakLimit > $teachersOnMealBreak) {
2992                        $teacherOnBreaks = $this->countTeacherOnBreaks($startDate, $endDate);
2993                        if ($breakLimitTotal > $teacherOnBreaks) {
2994                            $mealBreak = true;
2995                        }
2996                    }
2997
2998                    // check teacher break limit
2999                    if ($breakLimit > $teachersOnBreak) {
3000                        if($teacherOnBreaks == '') {
3001                            $teacherOnBreaks = $this->countTeacherOnBreaks($startDate, $endDate);
3002                        }
3003                        if ($breakLimitTotal > $teacherOnBreaks) {
3004                            $break = true;
3005                        }
3006                    }
3007                } else {
3008                    $mealBreak = true;
3009                    $break = true;
3010                }
3011            }
3012        }
3013
3014        $redirectNotStandby = false;
3015        //native teacher redirection
3016        $suddenLessonFlag = $this->TeacherRankCoin->findById($this->Auth->user('rank_coin_id'));
3017        if (isset($suddenLessonFlag['TeacherRankCoin']['sudden_lesson_flag']) && !$suddenLessonFlag['TeacherRankCoin']['sudden_lesson_flag']) {
3018            $redirectNotStandby = !(bool)$this->LessonSchedule->checkImpendingAndOngoingReservation($teacher_id);
3019        }
3020
3021        $result = array(
3022            'break' => $break,
3023            'mealBreak' => $mealBreak,
3024            'redirectNotStandby' => $redirectNotStandby,
3025            'hasOpenedSlot' =>  $this->getOpenedSlot()
3026        );
3027
3028        return json_encode($result);
3029    }
3030
3031    /**
3032     * @api {get} /teacher/api/checkCanBreak checkCanBreak()
3033     * @apiName checkCanBreak
3034     * @apiGroup API
3035     * @apiDescription checks if the current slot can open a break.
3036     * @apiSampleRequest off
3037     * 
3038     * @apiBody {String} break_id The ID of the break type.
3039     *
3040     * @apiSuccess {Boolean} can_break Indicates if the teacher can take a break.
3041     * @apiSuccess {Boolean} has_sub Indicates if the teacher is currently a substitute.
3042     * 
3043     * @apiErrorExample {json} Success-Response:
3044     *     {
3045     *       "can_break": true,
3046     *       "has_sub": false
3047     *     }
3048     *
3049     * @apiErrorExample {js} Used in: AngularJS
3050     * Location: "webroot/js/ng/controller/header.js"
3051     * Location: "webroot/js/ng/controller/schedule.js"
3052     */
3053    public function checkCanBreak() {
3054        $this->autoRender = false;
3055        $breakType = $this->request->query['break_id'];
3056        $break_time_start = date('H:i:s');
3057
3058        $minutes_now = date('i');
3059
3060        $time_start = date('H:30');
3061        $time_end = date('H:00', strtotime('+1 hour'));
3062
3063        if ($minutes_now >= 0 && $minutes_now < 30) {
3064            $time_start = date('H:00');
3065            $time_end = date('H:30');
3066        }
3067
3068        $breakLimit = null;
3069        $canBreak = false;
3070        //check if stealth teacher.
3071        $this->Teacher->recursive = -1;
3072        $stealth = $this->Teacher->find('count', array(
3073            'conditions' => array('id' => $this->Auth->user('id'), 'stealth_flg' => 1)
3074        ));
3075
3076        if ($stealth) {
3077            $result = array(
3078                'can_break' => true
3079            );
3080            return json_encode($result);
3081        }
3082        $this->BreakLimit->openDBReplica();
3083        $breakLimitInfo = $this->BreakLimit->find('first', array(
3084            'conditions' => array(
3085                "TIME_FORMAT(start_time, '%H:%i') = " => $time_start,
3086                "TIME_FORMAT(end_time, '%H:%i') = " => $time_end
3087            ),
3088            'fields' => array('BreakLimit.break', 'BreakLimit.meal', 'BreakLimit.start_time', 'BreakLimit.end_time', 'BreakLimit.total')
3089        ));
3090        $this->BreakLimit->closeDBReplica();
3091
3092        if (!empty($breakLimitInfo)) {
3093            $startDate = date('Y-m-d '.$breakLimitInfo['BreakLimit']['start_time']);
3094            $endDate = date('Y-m-d '.$breakLimitInfo['BreakLimit']['end_time']);
3095            if ($breakType == 1) {
3096                // meal break
3097                $breakLimit = $breakLimitInfo['BreakLimit']['meal'];
3098            } elseif ($breakType == 2) {
3099                // break
3100                $breakLimit = $breakLimitInfo['BreakLimit']['break'];
3101            }
3102            $breakLimitTotal =  $breakLimitInfo['BreakLimit']['total'];
3103            $countTeacherOnBreak = $this->TeacherStatus->find('count', array(
3104                'joins' => array(
3105                    array(
3106                        'table' => 'teachers',
3107                        'alias' => 'Teacher',
3108                        'type' => 'INNER',
3109                        'conditions' => array('TeacherStatus.teacher_id = Teacher.id', 'Teacher.stealth_flg = 0')
3110                    )
3111                ),
3112                'conditions' => array(
3113                    'TeacherStatus.status' => 4,
3114                    'TeacherStatus.remarks1' => $breakType,
3115                    'TeacherStatus.created BETWEEN ? AND ? ' => array($startDate, $endDate)
3116                )
3117            ));
3118            if ($breakLimit > $countTeacherOnBreak) {
3119                $countTeachersOnBreak = $this->countTeacherOnBreaks($startDate, $endDate);
3120                if ($breakLimitTotal > $countTeachersOnBreak) {
3121                    $canBreak = true;
3122                }
3123            }
3124        } else {
3125            if ($breakType == 2) {
3126                $canBreak = true;
3127            }
3128        }
3129
3130        //this will check if teacher is currently a sub after teacher auto cancellation
3131        $memcached = new myMemcached();
3132        $lessonTime = date('Y-m-d H:00:00');
3133        if (date('i') >= 30) {
3134            $lessonTime = date('Y-m-d H:30:00');
3135        }
3136        $subteacherIds = $memcached->get('auto_cancelation_substitute_'.strtotime($lessonTime));
3137        $subTeacherArray = array();
3138        $hasSubLesson = false;
3139        if ($subteacherIds) {
3140            $subTeacherArray = explode(",", $subteacherIds);
3141            $hasSubLesson = ($subTeacherArray && in_array($this->Auth->user('id'), $subTeacherArray)) ? true : false;
3142        }
3143
3144        $result = array(
3145            'can_break' => $canBreak,
3146            'has_sub' => $hasSubLesson
3147        );
3148        return json_encode($result);
3149    }
3150
3151    /**
3152     * @api {get} /teacher/api/checkCanBreakSlot checkCanBreakSlot()
3153     * @apiName checkCanBreakSlot
3154     * @apiGroup API
3155     * @apiDescription checks if the current slot can open a meal break.
3156     * @apiSampleRequest off
3157     * 
3158     * @apiBody {String} break_time_start The start time of the break.
3159     *
3160     * @apiSuccess {Boolean} can_break Indicates if the teacher can take a break.
3161     * @apiSuccess {Boolean} teacher_mb_limit Indicates if the teacher has reached the meal break limit.
3162     * 
3163     * @apiErrorExample {json} Success-Response:
3164     *     {
3165     *       "can_break": true,
3166     *       "teacher_mb_limit": false
3167     *     }
3168     *
3169     * @apiErrorExample {json} Error-Response:
3170     *     {
3171     *       "can_break": false,
3172     *       "teacher_mb_limit": true
3173     *     }
3174      * @apiErrorExample {js} Used in: AngularJS
3175      * Location: "webroot/js/ng/controller/schedule.js"
3176     */
3177    // NC-5554 : check if the slot can open a meal break
3178    public function checkCanBreakSlot () {
3179        $this->autoRender = false;
3180        $break_time_start = isset($this->request->query['break_time_start']) ? $this->request->query['break_time_start'] : NULL;
3181
3182        if ($break_time_start) {
3183
3184            $time_start = date('H:00', strtotime($break_time_start));
3185            $time_end = date('H:30', strtotime($break_time_start));
3186
3187            if (date('i', strtotime($break_time_start)) == '30') {
3188                $time_start = date('H:30', strtotime($break_time_start));
3189                $time_end = date('H:00', strtotime($break_time_start . ' +1 hour'));
3190            }
3191
3192        } else {
3193            $result = array(
3194                'can_break' => false,
3195                'teacher_mb_limit' => false
3196            );
3197            return json_encode($result);
3198        }
3199
3200        $from = date('Y-m-d 03:00:00', strtotime($break_time_start));
3201        $to = date('Y-m-d 02:59:00', strtotime($break_time_start . ' +1 days'));
3202        $time = date('H:i:s', strtotime($break_time_start));
3203
3204        if ($time == '00:00:00' || $time == '00:30:00'
3205            || $time == '01:00:00' || $time == '01:30:00'
3206            || $time == '02:00:00' || $time == '02:30:00'
3207        ) {
3208            $from = date('Y-m-d 03:00:00', strtotime($break_time_start . ' -1 days'));
3209            $to = date('Y-m-d 02:59:00', strtotime($break_time_start));
3210        }
3211
3212
3213        // check if teaher has 2 slots already for the day.
3214        $limitPerDay = $this->ShiftWorkMealBreak->find('count', array(
3215            'conditions' => array(
3216                'teacher_id' => $this->Auth->user('id'),
3217                'lesson_time >=' => $from,
3218                'lesson_time <=' => $to
3219            )
3220        ));
3221
3222        if ($limitPerDay && $limitPerDay >= Configure::read('office.mealbreak_limit')) {
3223            $result = array(
3224                'can_break' => false,
3225                'teacher_mb_limit' => true
3226            );
3227            return json_encode($result);
3228        }
3229
3230        $breakLimit = null;
3231        $canBreak = false;
3232        //check if stealth teacher.
3233        $this->Teacher->recursive = -1;
3234        $stealth = $this->Teacher->find('count', array(
3235            'conditions' => array('id' => $this->Auth->user('id'), 'stealth_flg' => 1)
3236        ));
3237
3238        if ($stealth) {
3239            $result = array(
3240                'can_break' => true,
3241                'teacher_mb_limit' => false
3242            );
3243            return json_encode($result);
3244        }
3245        $breakLimitInfo = $this->BreakLimit->find('first', array(
3246            'conditions' => array(
3247                "TIME_FORMAT(start_time, '%H:%i') = " => $time_start,
3248                "TIME_FORMAT(end_time, '%H:%i') = " => $time_end
3249            ),
3250            'fields' => array('BreakLimit.break', 'BreakLimit.meal', 'BreakLimit.start_time', 'BreakLimit.end_time', 'BreakLimit.total')
3251        ));
3252
3253        if (!empty($breakLimitInfo)) {
3254            $startDate = date('Y-m-d '.$breakLimitInfo['BreakLimit']['start_time']);
3255            $endDate = date('Y-m-d '.$breakLimitInfo['BreakLimit']['end_time']);
3256
3257            // meal break
3258            $breakLimit = $breakLimitInfo['BreakLimit']['meal'];
3259
3260            $breakLimitTotal =  $breakLimitInfo['BreakLimit']['total'];
3261            $countTeacherOnBreak = $this->ShiftWorkMealBreak->find('count', array(
3262                'conditions' => array('ShiftWorkMealBreak.lesson_time' => $break_time_start)
3263            ));
3264            if ($breakLimit > $countTeacherOnBreak) {
3265                $countTeachersOnBreak = $this->countTeacherOnBreaks($startDate, $endDate);
3266                if ($breakLimitTotal > $countTeachersOnBreak) {
3267                    $canBreak = true;
3268                }
3269            }
3270        }
3271
3272        $result = array(
3273            'can_break' => $canBreak,
3274            'teacher_mb_limit' => false
3275        );
3276        return json_encode($result);
3277    }
3278
3279    private function countTeacherOnBreaks($startDate = null, $endDate = null) {
3280        $this->TeacherStatus->openDBReplica();
3281        $countTeachersOnBreak = $this->TeacherStatus->find('count', array(
3282            'joins' => array(
3283                array(
3284                    'table' => 'teachers',
3285                    'alias' => 'Teacher',
3286                    'type' => 'LEFT',
3287                    'conditions' => array('TeacherStatus.teacher_id = Teacher.id')
3288                )
3289            ),
3290            'conditions' => array(
3291                'TeacherStatus.status' => '4',
3292                'TeacherStatus.remarks1' => array('1','2'),
3293                'TeacherStatus.created >= ' => $startDate,
3294                'TeacherStatus.created <= ' => $endDate,
3295                'Teacher.stealth_flg' => "0"
3296            )
3297        ));
3298        $this->TeacherStatus->closeDBReplica();
3299        return $countTeachersOnBreak;
3300    }
3301
3302    /**
3303     * @api {get} /teacher/api/getReservedOrAvailableOnMealBreak getReservedOrAvailableOnMealBreak()
3304     * @apiName getReservedOrAvailableOnMealBreak
3305     * @apiGroup API
3306     * @apiDescription checks if the teacher has a reservation or is available within the meal break time.
3307     * @apiSampleRequest off
3308     * 
3309     * @apiSuccess {Boolean} notify Indicates if the teacher has a reservation or is available within the meal break time.
3310     * 
3311     * @apiErrorExample {json} Success-Response:
3312     *     {
3313     *       "notify": true
3314     *     }
3315     * 
3316      * @apiErrorExample {js} Used in: AngularJS
3317      * Location: "webroot/js/ng/controller/header.js"
3318     */
3319    /**
3320     * Check if teacher has reservaton or is available within meal break time.
3321     */
3322    public function getReservedOrAvailableOnMealBreak() {
3323        $this->autoRender = false;
3324        if (date('i') <= 30) {
3325            $startDate = date('Y-m-d H:30:00');
3326        } else {
3327            $startDate = date('Y-m-d H:00:00', strtotime('+1 hour'));
3328        }
3329
3330        $endDate = date('Y-m-d H:i:00', strtotime('+1 hour', strtotime($startDate)));
3331        $this->ShiftWorkOn->openDBReplica();
3332        $slots = $this->ShiftWorkOn->find('count', array(
3333            'conditions' => array(
3334                'ShiftWorkOn.teacher_id' => $this->Auth->user('id'),
3335                'ShiftWorkOn.lesson_time >=' => $startDate,
3336                'ShiftWorkOn.lesson_time <' => $endDate
3337            )
3338        ));
3339        $this->ShiftWorkOn->closeDBReplica();
3340        // if has shift available
3341        if ($slots) {
3342            return json_encode(array('notify' => true));
3343        }
3344        return json_encode(array('notify' => false));
3345    }
3346
3347    /**
3348     * @api {get} /teacher/api/canLesson canLesson()
3349     * @apiName canLesson
3350     * @apiGroup API
3351     * @apiDescription checks if the teacher can lesson.
3352     * @apiSampleRequest off
3353     * 
3354     * @apiSuccess {Boolean} canLesson Indicates if the teacher can lesson.
3355     * 
3356     * @apiErrorExample {json} Success-Response:
3357     *     {
3358     *       "canLesson": true
3359     *     }
3360     *
3361     * @apiErrorExample {json} Error-Response:
3362     *     {
3363     *       "canLesson": false
3364     *     }
3365     */
3366    /**
3367     * Check if teacher can lesson
3368     */
3369    public function canLesson() {
3370        $this->autoRender = false;
3371        $data = $this->LessonOnair->find('first', array(
3372            'fields' => array('connect_flg', 'status'),
3373            'conditions' => array('teacher_id' => $this->Auth->user('id')),
3374            'recursive' => -1
3375        ));
3376
3377        if(!isset($data['LessonOnair'])) {
3378            return json_encode(array('canLesson' => true));
3379        }
3380        else if ($data['LessonOnair']['status'] == 3 || $data['LessonOnair']['connect_flg'] == 1) {
3381            return json_encode(array('canLesson' => true));
3382        } else {
3383            return json_encode(array('canLesson' => false));
3384        }
3385    }
3386
3387    /**
3388     * @api {post} /teacher/api/studentDisconnection studentDisconnection()
3389     * @apiName studentDisconnection
3390     * @apiGroup API
3391     * @apiDescription will be called whenever the teacher is in the ongoing lesson state.
3392     * @apiSampleRequest off
3393     * 
3394     * @apiBody {String} chatHash The chat hash.
3395     *
3396     * @apiSuccess {boolean} error Indicates if there is an error.
3397     * @apiSuccess {String} content The content of the response.
3398     * 
3399     * @apiErrorExample {json} Success-Response:
3400     *     {
3401     *       "error": false,
3402     *       "content": ""
3403     *     }
3404     *
3405     * @apiErrorExample {json} Error-Response:
3406     *     {
3407     *       "error": true,
3408     *       "content": "invalid_request"
3409     *     }
3410     */
3411    /**
3412     * this function will be called
3413     * whenever the teacher is in the ongoing lesson state
3414     */
3415    public function studentDisconnection(){
3416        $this->autoRender = false;
3417
3418        // only allow ajax requests
3419        if (!$this->request->is('ajax')) {
3420            return json_encode(array('error' => true, 'content' => 'invalid_request'));
3421        }
3422
3423        // set post data
3424        $post = $this->request->data;
3425
3426        // check if chathash exists
3427        if (!isset($post['chatHash'])) {
3428            return json_encode(array('error' => true, 'content' => 'invalid_params'));
3429        }
3430
3431        // get vars
3432        $chatHash = $post['chatHash'];
3433
3434        // declare myMemcached
3435        $memcached = new myMemcached();
3436
3437        // set
3438        $memcached->set(array('key' => 'DC_' . $chatHash, 'value' => time(), 'expire' => 3600 ));
3439
3440        // return reservation
3441        return json_encode(array('error' => false, 'content' => ''));
3442    }
3443
3444    /**
3445     * @api {get} /teacher/api/setValidLesson setValidLesson()
3446     * @apiName setValidLesson
3447     * @apiGroup API
3448     * @apiDescription updates the validity flag of the lesson.
3449     * @apiSampleRequest off
3450     * 
3451     * @apiBody {String} chat_hash The chat hash of the lesson.
3452     * @apiBody {Boolean} valid_flg The validity flag to set.
3453     *
3454     * @apiSuccess {boolean} result Indicates if the operation was successful.
3455     * @apiSuccess {String} message The message of the response.
3456     * 
3457     * @apiErrorExample {json} Success-Response:
3458     *     {
3459     *       "result": true,
3460     *       "message": "lesson_memo_valid_flg has changed"
3461     *     }
3462     *
3463     * @apiErrorExample {json} Error-Response:
3464     *     {
3465     *       "result": false,
3466     *       "message": "lesson on air not found"
3467     *     }
3468     */
3469    /**
3470     * Update valid_flg of onairs
3471     */
3472    public function setValidLesson() {
3473        $this->autoRender = false;
3474
3475        $data = $this->request->data;
3476        $lesson = $this->LessonOnair->find('first', array(
3477            'fields' => array('id'),
3478            'conditions' => array('chat_hash' => $data['chat_hash'])
3479        ));
3480
3481        $res['result'] = false;
3482        if (empty($lesson['LessonOnair'])) {
3483            $res['message'] = 'lesson on air not found';
3484        } else {
3485            $res['result'] = true;
3486            $res['message'] = 'lesson_memo_valid_flg has changed';
3487            $this->LessonOnair->read(null, $lesson['LessonOnair']['id']);
3488            $this->LessonOnair->set('lesson_memo_valid_flg', $data['valid_flg']);
3489            $this->LessonOnair->save();
3490        }
3491        return json_encode($res);
3492    }
3493
3494    /**
3495     * @api {get} /teacher/api/countUnreadAppreciationMessage countUnreadAppreciationMessage()
3496     * @apiName countUnreadAppreciationMessage
3497     * @apiGroup API
3498     * @apiDescription counts the unread appreciation message.
3499     * @apiSampleRequest off
3500     * 
3501     * @apiSuccess {int} countUnread The count of unread appreciation message.
3502     * 
3503     * @apiErrorExample {json} Success-Response:
3504     *     {
3505     *       "countUnread": 5
3506     *     }
3507     *
3508     * @apiErrorExample {json} Error-Response:
3509     *     {
3510     *       "countUnread": 0
3511     *     }
3512     * 
3513      * @apiErrorExample {js} Used in: AngularJS
3514      * Location: "webroot/js/ng/controller/home.js"
3515      * Location: "webroot/js/ng/app.js"
3516      * Location: "webroot/js/ng/home.js"
3517     */
3518    /**
3519     * Count unread appreciation message 
3520     * @return int countmessage
3521     */
3522
3523    public function countUnreadAppreciationMessage(){
3524        $this->autoRender = false;
3525
3526        $teacherId = $this->Auth->user('id');
3527
3528        //count unread appreciation 
3529        $countUnread = $this->TeacherCoinBox->countUnreadAppreciation($teacherId);
3530
3531        return $countUnread;
3532    }
3533
3534    /**
3535     * @api {post} /teacher/api/message_badge countUnsentMessage()
3536     * @apiName countUnsentMessage
3537     * @apiGroup API
3538     * @apiDescription counts the unsent message / drafts.
3539     * @apiSampleRequest off
3540     * 
3541     * @apiBody {String} teachers_api_token The API token of the teacher.
3542     *
3543     * @apiSuccess {int} badge The count of unsent message / drafts.
3544     * 
3545     * @apiErrorExample {json} Success-Response:
3546     *     {
3547     *       "badge": 5
3548     *     }
3549     * 
3550      * @apiErrorExample {js} Used in: AngularJS
3551      * Location: "webroot/js/ng/app.js"
3552     */
3553    /**
3554     * Count unsent message / drafts
3555     * @return int countdraft
3556     */
3557    public function countUnsentMessage() {
3558        $this->autoRender = false;
3559
3560        if ($this->request->is('post')) {
3561
3562            $data = json_decode($this->request->input(), true);
3563
3564            if($data){
3565                $teacher = $this->Teacher->getFromToken($data['teachers_api_token']);
3566
3567                if(empty($teacher)){
3568                    $response['error']['id'] = Configure::read('error.missing_teacher');
3569                    $response['error']['message'] = __('invalid teacher');
3570                    return json_encode($response);
3571                }
3572
3573                $onairsDrafts = $this->LessonOnair->countDrafts($teacher['id']);
3574                $onairsLogDrafts = $this->LessonOnairsLog->countDrafts($teacher['id']);
3575                $countDrafts = $onairsDrafts + $onairsLogDrafts;
3576                
3577                return json_encode(['badge' => $countDrafts]);
3578            } else {
3579
3580                $teacher_id = $this->Auth->user('id');
3581                // count unsent messages
3582                $onairsDrafts = $this->LessonOnair->countDrafts($teacher_id);
3583                $onairsLogDrafts = $this->LessonOnairsLog->countDrafts($teacher_id);
3584                $countDrafts = $onairsDrafts + $onairsLogDrafts;
3585    
3586            }
3587            
3588        } 
3589        
3590        return $countDrafts;
3591    }
3592
3593    /**
3594     * @api {post} /teacher/api/getlastCallanStage getlastCallanStage()
3595     * @apiName getlastCallanStage
3596     * @apiGroup API
3597     * @apiDescription gets the last callan stage number of user and teacher.
3598     * @apiSampleRequest off
3599     * 
3600     * @apiBody {String} user_id The ID of the user.
3601     *
3602     * @apiSuccess {boolean} result Indicates if the operation was successful.
3603     * @apiSuccess {int} last_callan_stage The last callan stage number.
3604     * @apiSuccess {String} message The message of the response.
3605     * 
3606     * @apiErrorExample {json} Success-Response:
3607     *     {
3608     *       "result": true,
3609     *       "last_callan_stage": 3
3610     *     }
3611     *
3612     * @apiErrorExample {json} Error-Response:
3613     *     {
3614     *       "result": false,
3615     *       "message": "missing required values"
3616     *     }
3617     * 
3618      * @apiErrorExample {js} Used in: AngularJS
3619      * Location: "webroot/js/ng/app.js"
3620     */
3621    /**
3622     * gets the last callan stage number of user and teacher
3623     */
3624    public function getlastCallanStage() {
3625        $this->autoRender = false;
3626
3627        $data = $this->request->data;
3628        if (empty($data) || empty($data['user_id'])) {
3629            return json_encode(array('result' => false, 'message' => 'missing required values'));
3630        }
3631        $lastCallanStage = false;
3632
3633        $regCallanParams = array( 'userId' => $data['user_id'] ,'callanType' => 2,'control_field' => "callan_info");
3634        $result = $this->LessonOnairsLog->latestCallanLesson($regCallanParams);
3635        // if last callan stage does not exist
3636        $res = array('result' => false, 'last_callan_stage' => 0);
3637        if (empty($result)) {
3638            $res['message'] = 'no result found';
3639        } else {
3640            // parse
3641            $jsonLastCallan = json_decode($result['LessonMessage']['lesson_memo']);
3642            $lastCallanStage = 0;
3643            // if last staging is set
3644            if (isset($jsonLastCallan->message_15[0]) && $jsonLastCallan->message_15[0] != "Not Finished") {
3645                $lastCallanStage = explode(" ", $jsonLastCallan->message_15[0]);
3646                $lastCallanStage = isset($lastCallanStage[1]) ? intVal($lastCallanStage[1]) : 0;
3647            } else if (isset($jsonLastCallan->message_17) && $jsonLastCallan->message_17 != "Not Finished") {
3648                $lastCallanStage = explode(" ", $jsonLastCallan->message_17);
3649                $lastCallanStage = isset($lastCallanStage[1]) ? intVal($lastCallanStage[1]) : 0;
3650            }
3651            $res['last_callan_stage'] = $lastCallanStage;
3652            $res['result'] = true;
3653        }
3654        return json_encode($res);
3655    }
3656
3657    /**
3658     * @api {get} /teacher/api/callStartTeacherApi callStartTeacherApi()
3659     * @apiName callStartTeacherApi
3660     * @apiGroup API
3661     * @apiDescription initializes the lesson start.
3662     * @apiSampleRequest off
3663     * 
3664     * @apiBody {String} chatHash The chat hash.
3665     * @apiBody {Boolean} isIncludeLessonCount Whether to include the lesson count.
3666     * @apiBody {String} studentLessonLocalizeDir The student's lesson localization directory.
3667     *
3668     * @apiSuccess {boolean} error Indicates if there is an error.
3669     * @apiSuccess {Object} content The content of the response.
3670     * @apiSuccess {String} content.studentImage The student's image URL.
3671     * @apiSuccess {String} content.studentName The student's name.
3672     * @apiSuccess {int} content.studentLessonCount The student's lesson count.
3673     * @apiSuccess {int} content.lessonType The lesson type.
3674     * 
3675     * @apiErrorExample {json} Success-Response:
3676     *     {
3677     *       "error": false,
3678     *       "content": {
3679     *         "studentImage": "image_url",
3680     *         "studentName": "Student Name",
3681     *         "studentLessonCount": 5,
3682     *         "lessonType": 1,
3683     *         ...
3684     *       }
3685     *     }
3686     *
3687     * @apiErrorExample {json} Error-Response:
3688     *     {
3689     *       "error": true,
3690     *       "content": "Error message"
3691     *     }
3692     */
3693    /**
3694     * initialize lesson start
3695     */
3696    public function callStartTeacherApi () {
3697        $this->autoRender = false;
3698        $hasLessonCount = 0;//prevent warning
3699
3700        // - default language
3701        $defaultLanguage = Configure::read('english_language_id');
3702        $engLanguage = "en";
3703        
3704        // return view vars
3705        $viewVars = array(
3706            'studentImage' => "",
3707            'studentName' => "Missing No.",
3708            'studentLessonCount' => 0,
3709            'lessonType' => 1
3710        );
3711
3712        // only allow ajax requests
3713        if (!$this->request->is('ajax')) {
3714            return json_encode(array('error' => true, 'content' => 'invalid_request'));
3715        }
3716
3717        // set post data
3718        $post = $this->request->data;
3719
3720        // check if chathash exists
3721        if (!isset($post['chatHash'])) {
3722            return json_encode(array('error' => true, 'content' => 'invalid_params'));
3723        }
3724
3725        // get vars
3726        $chatHash = $post['chatHash'];
3727        $hasLessonCount = isset($post['isIncludeLessonCount']) ? $post['isIncludeLessonCount'] : null;
3728        $userLocale = (isset($post['studentLessonLocalizeDir']) && $post['studentLessonLocalizeDir']) ? $post['studentLessonLocalizeDir'] : Configure::read('default.user_language');
3729
3730        // get lessononairs
3731        $lessonOnair = $this->LessonOnair->find('first', array(
3732            'fields' => array(
3733                'LessonOnair.id',
3734                'LessonOnair.chat_hash',
3735                'LessonOnair.user_id',
3736                'LessonOnair.teacher_id',
3737                'LessonOnair.lesson_type',
3738                'LessonOnair.lesson_finish',
3739                'LessonOnair.user_agent',
3740                'LessonOnair.connect_id',
3741                'LessonOnair.requested_lesson_time',
3742                'LessonOnair.lesson_schedule_id',
3743                'LessonRequest.self_introduction',
3744                'LessonRequest.incorrect_grammar',
3745                'LessonRequest.other_request',
3746                'TextbookSubCategory.id',
3747                'TextbookSubCategory.name',
3748                'TextbookSubCategory.english_name',
3749                'GlobalTextbookSubCategory.gl_name',
3750                'Textbook.id',
3751                'Textbook.name',
3752                'Textbook.name_eng',
3753                'Textbook.chapter_id',
3754                'Textbook.main_topic_id',
3755                'GlobalTextbook.gl_name',
3756                'TextbookCategory.textbook_category_type',
3757                'TextbookCategory.id',
3758                'TextbookCategory.type_id',
3759                'TextbookCategory.name',
3760                'TextbookCategory.english_name',
3761                'TextbookCategory.audio_acquisition_flg',
3762                'GlobalTextbookCategory.gl_name',
3763                'LessonOnair.connect_id',
3764                'LessonOnair.textbook_language',
3765                'User.native_language2',
3766                'LessonOnair.chocotto_camp_lesson_flg'
3767            ),
3768            'conditions' => array(
3769                'chat_hash' => $chatHash
3770            ),
3771            'joins' => array (
3772                array(
3773                    'type' => 'LEFT',
3774                    'table' => 'lesson_requests',
3775                    'alias' => 'LessonRequest',
3776                    'conditions' => 'LessonOnair.user_id = LessonRequest.user_id'
3777                ),
3778                array(
3779                    'type' => 'LEFT',
3780                    'table' => 'textbook_connects',
3781                    'alias' => 'TextbookConnect',
3782                    'conditions' => 'LessonOnair.connect_id = TextbookConnect.id'
3783                    ),
3784                array(
3785                    'type' => 'LEFT',
3786                    'table' => 'textbook_subcategories',
3787                    'alias' => 'TextbookSubCategory',
3788                    'conditions' => 'TextbookConnect.subcategory_id = TextbookSubCategory.id'
3789                ),
3790                array(
3791                    'type' => 'LEFT',
3792                    'table' => 'global_textbook_subcategories',
3793                    'alias' => 'GlobalTextbookSubCategory',
3794                    'conditions' => 'TextbookSubCategory.id = GlobalTextbookSubCategory.textbook_subcategory_id AND GlobalTextbookSubCategory.language_id = ' . $defaultLanguage
3795                ),
3796                array(
3797                    'type' => 'LEFT',
3798                    'table' => 'textbooks',
3799                    'alias' => 'Textbook',
3800                    'conditions' => 'TextbookConnect.textbook_id = Textbook.id'
3801                ),
3802                array(
3803                    'type' => 'LEFT',
3804                    'table' => 'global_textbooks',
3805                    'alias' => 'GlobalTextbook',
3806                    'conditions' => 'Textbook.id = GlobalTextbook.textbook_id AND GlobalTextbook.language_id = ' . $defaultLanguage
3807                ),
3808                array(
3809                    'type' => 'LEFT',
3810                    'table' => 'textbook_categories',
3811                    'alias' => 'TextbookCategory',
3812                    'conditions' => 'TextbookConnect.category_id = TextbookCategory.id'
3813                ),
3814                array(
3815                    'type' => 'LEFT',
3816                    'table' => 'global_textbook_categories',
3817                    'alias' => 'GlobalTextbookCategory',
3818                    'conditions' => 'TextbookCategory.id = GlobalTextbookCategory.textbook_category_id AND GlobalTextbookCategory.language_id = ' . $defaultLanguage
3819                ),
3820                array(
3821                    'type' => 'LEFT',
3822                    'table' => 'users',
3823                    'alias' => 'User',
3824                    'conditions' => array( 'User.id = LessonOnair.user_id' )
3825                )
3826            ),
3827            'recursive' => -1
3828        ));
3829
3830        // check if chatHash is related to a legit lessonOnairs
3831        if (!$lessonOnair) {
3832            return json_encode(array('error' => true, 'content' => 'empty_lesson_onairs'));
3833        }
3834
3835        $pcDeviceType = "PC";
3836        //get user device
3837        $studentDeviceType = myTools::getUsersDevice($lessonOnair['LessonOnair']['user_agent']); 
3838        //for mobile lesson
3839        //if device type is not a PC use student language to get the global translation
3840        if ((isset($studentDeviceType) && $studentDeviceType) && $pcDeviceType != $studentDeviceType) {
3841            $userLocale = (isset($lessonOnair['User']['native_language2']) && $lessonOnair['User']['native_language2']) ? $lessonOnair['User']['native_language2'] : 'en';
3842        } else {
3843            if ( isset($lessonOnair['LessonOnair']['textbook_language']) && $lessonOnair['LessonOnair']['textbook_language'] ) {
3844                $userLocale = $lessonOnair['LessonOnair']['textbook_language'];
3845            }
3846        }
3847
3848        // - last lesson
3849        $userLastLesson = $this->LessonOnairsLog->query("
3850            SELECT
3851                -- main fields
3852                `LessonOnairsLog`.`id`,
3853                `LessonOnairsLog`.`end_time`,
3854                `TextbookConnect`.`id`,
3855                `TextbookCategory`.`name`,
3856                `TextbookCategory`.`english_name`,
3857                `TextbookSubCategory`.`name`,
3858                `Textbook`.`id`,
3859                `Textbook`.`name`,
3860                `TextbookSubCategory`.`english_name`,
3861                `Textbook`.`name_eng`,
3862                `Textbook`.`main_topic_id`,
3863                `GlobalTextbookCategory`.`gl_name`,
3864                `GlobalTextbookSubCategory`.`gl_name`,
3865                `GlobalTextbook`.`gl_name`,
3866
3867                -- count lesson
3868                ((
3869                    SELECT
3870                        COUNT(`LessonLog`.`id`)
3871                    FROM
3872                        lesson_onairs_logs AS LessonLog
3873                    WHERE
3874                        `LessonLog`.`user_id` = `LessonOnairsLog`.`user_id`
3875                        AND `LessonLog`.`teacher_id` = `LessonOnairsLog`.`teacher_id`
3876                        AND `LessonLog`.`start_time` IS NOT NULL
3877                        AND `LessonLog`.`end_time` IS NOT NULL
3878                        AND `LessonLog`.`connect_id` IS NOT NULL
3879                )) AS `count_lesson`
3880
3881            -- main table
3882            FROM
3883                `english`.`lesson_onairs_logs` AS `LessonOnairsLog` FORCE INDEX (user_id)
3884
3885            -- join conditions
3886            LEFT JOIN
3887            `english`.`textbook_connects` AS `TextbookConnect` ON (`LessonOnairsLog`.`connect_id` = `TextbookConnect`.`id`)
3888            LEFT JOIN
3889            `english`.`textbook_categories` AS `TextbookCategory` ON (`TextbookConnect`.`category_id` = `TextbookCategory`.`id`)
3890            LEFT JOIN
3891            `english`.`textbook_subcategories` AS `TextbookSubCategory` ON (`TextbookConnect`.`subcategory_id` = `TextbookSubCategory`.`id`)
3892            LEFT JOIN
3893            `english`.`textbooks` AS `Textbook` ON (`TextbookConnect`.`textbook_id` = `Textbook`.`id`)
3894            LEFT JOIN
3895            `english`.`global_textbook_categories` AS `GlobalTextbookCategory` ON (
3896                `TextbookCategory`.`id` = `GlobalTextbookCategory`.`textbook_category_id`
3897                AND `GlobalTextbookCategory`.`language_id` = " . $defaultLanguage . "
3898            )
3899            LEFT JOIN
3900            `english`.`global_textbook_subcategories` AS `GlobalTextbookSubCategory` ON (
3901                `TextbookSubCategory`.`id` = `GlobalTextbookSubCategory`.`textbook_subcategory_id`
3902                AND `GlobalTextbookSubCategory`.`language_id` = " . $defaultLanguage . "
3903            )
3904            LEFT JOIN
3905            `english`.`global_textbooks` AS `GlobalTextbook` ON (
3906                `Textbook`.`id` = `GlobalTextbook`.`textbook_id`
3907                AND `GlobalTextbook`.`language_id` = " . $defaultLanguage . "
3908            )
3909
3910            -- where conditions
3911            WHERE
3912                `LessonOnairsLog`.`user_id` = " . $lessonOnair['LessonOnair']['user_id'] . "
3913                AND `LessonOnairsLog`.`teacher_id` = " . $this->Auth->user('id') . "
3914                AND `LessonOnairsLog`.`connect_id` IS NOT NULL
3915                AND `LessonOnairsLog`.`start_time` IS NOT NULL
3916                AND `LessonOnairsLog`.`end_time` IS NOT NULL
3917            ORDER BY `LessonOnairsLog`.`end_time` DESC
3918            LIMIT 1
3919        ");
3920        
3921
3922        // - get result - [0] index
3923        $userLastLesson = $userLastLesson[0];
3924
3925        $dataLastLesson = $this->LessonOnairsLog->userLastLessons($lessonOnair['LessonOnair']['user_id'], $lessonOnair['LessonOnair']['connect_id']);
3926        $viewVars['display_get_request'] = $this->LessonSchedule->userCurrentLessonSchedule($lessonOnair['LessonOnair']['user_id'], $lessonOnair['LessonOnair']['lesson_schedule_id']);
3927        $viewVars['lesson_memo_disp_flg'] = isset($dataLastLesson['LessonOnairsLog']['lesson_memo_disp_flg']) ? $dataLastLesson['LessonOnairsLog']['lesson_memo_disp_flg'] : '';
3928        $viewVars['lesson_memo'] = isset($dataLastLesson['LessonOnairsLog']['lesson_memo']) ? $dataLastLesson['LessonOnairsLog']['lesson_memo'] : '';
3929        $viewVars['user_id'] = isset($dataLastLesson['LessonOnairsLog']['user_id']) ? $dataLastLesson['LessonOnairsLog']['user_id'] : '';
3930
3931        $memcached = new myMemcached();
3932        $textbookNamesCached = $memcached->get(Configure::read('textbook_names_cache_key'));
3933        $order = (isset($textbookNamesCached[$lessonOnair['LessonOnair']['connect_id']]['order']) && $textbookNamesCached[$lessonOnair['LessonOnair']['connect_id']]['order']) ? $textbookNamesCached[$lessonOnair['LessonOnair']['connect_id']]['order'] : '';
3934        $orderLastLesson = (isset($textbookNamesCached[$userLastLesson['TextbookConnect']['id']]['order']) && $textbookNamesCached[$userLastLesson['TextbookConnect']['id']]['order']) ? $textbookNamesCached[$userLastLesson['TextbookConnect']['id']]['order'] : '';
3935
3936        // GET GLOBAL TRANSLATION OF TEXTBOOK, CATEGORY AND SUBCATEGORY DATA
3937        $glTextbook = ClassRegistry::init('GlobalTextbookTable')->getGlobalData($lessonOnair['Textbook']['id']);
3938        $glTextbookCategory = ClassRegistry::init('GlobalTextbookCategoryTable')->getGlobalData($lessonOnair['TextbookCategory']['id']);
3939        $glTextbookSubcategory = ClassRegistry::init('GlobalTextbookSubcategoryTable')->getGlobalData($lessonOnair['TextbookSubCategory']['id']);
3940
3941        // - set variables
3942        // SET TEXTBOOK AND SUBCATEGORY TITLE BY USING STUDENT LANGUAGE
3943        $textBookChapterName = $lessonOnair['Textbook']['name'];
3944        if ($userLocale != Configure::read('default.user_language')) {
3945            if ($userLocale != $engLanguage) {
3946                if (isset($glTextbook[$userLocale]['gl_name']) && $glTextbook[$userLocale]['gl_name']) {
3947                    $textBookChapterName = $glTextbook[$userLocale]['gl_name'];
3948                } else {
3949                    $textBookChapterName = $lessonOnair['Textbook']['name_eng'];
3950                }
3951            } else {
3952                $textBookChapterName = $lessonOnair['Textbook']['name_eng'];
3953            }
3954        }
3955        
3956        $textBookChapterNameEng = '';
3957        if (isset($lessonOnair['Textbook']['name_eng']) && $lessonOnair['Textbook']['name_eng']) {
3958            $textBookChapterNameEng = $lessonOnair['Textbook']['name_eng'];
3959        } elseif ($userLocale != $engLanguage) {
3960            if (isset($glTextbook[$engLanguage]['gl_name']) && $glTextbook[$engLanguage]['gl_name']) {
3961                $textBookChapterNameEng = $glTextbook[$engLanguage]['gl_name'];
3962            }
3963        }
3964        
3965        $textbookClassName = $lessonOnair['TextbookSubCategory']['english_name'];
3966        if ($userLocale != Configure::read('default.user_language')) {
3967            if ($userLocale != $engLanguage) {
3968                if (isset($glTextbookSubcategory[$userLocale]['gl_name']) && $glTextbookSubcategory[$userLocale]['gl_name']) {
3969                    $textbookClassName = $glTextbookSubcategory[$userLocale]['gl_name'];
3970                } else {
3971                    $textbookClassName = $lessonOnair['TextbookSubCategory']['english_name'];
3972                }
3973            } else {
3974                $textbookClassName = $lessonOnair['TextbookSubCategory']['english_name'];
3975            }
3976        }
3977        
3978        $textbookClassNameEng = '';
3979        if (isset($lessonOnair['TextbookSubCategory']['english_name']) && $lessonOnair['TextbookSubCategory']['english_name']) {
3980            $textbookClassNameEng = $lessonOnair['TextbookSubCategory']['english_name'];
3981        } elseif ($userLocale != $engLanguage) {
3982            if (isset($glTextbookSubcategory[$engLanguage]['gl_name']) && $glTextbookSubcategory[$engLanguage]['gl_name']) {
3983                $textbookClassNameEng = $glTextbookSubcategory[$engLanguage]['gl_name'];
3984            }
3985        }
3986        
3987        $textbookCategoryName = $lessonOnair['TextbookCategory']['english_name'];
3988        if ($userLocale != Configure::read('default.user_language')) {
3989            if ($userLocale != $engLanguage) {
3990                if (isset($glTextbookCategory[$userLocale]['gl_name']) && $glTextbookCategory[$userLocale]['gl_name']) {
3991                    $textbookCategoryName = $glTextbookCategory[$userLocale]['gl_name'];
3992                } else {
3993                    $textbookCategoryName = $lessonOnair['TextbookCategory']['english_name'];
3994                }
3995            } else {
3996                $textbookCategoryName = $lessonOnair['TextbookCategory']['english_name'];
3997            }
3998        }
3999        
4000        $textbookCategoryNameEng = '';
4001        if (isset($lessonOnair['TextbookCategory']['english_name']) && $lessonOnair['TextbookCategory']['english_name']) {
4002            $textbookCategoryNameEng = $lessonOnair['TextbookCategory']['english_name'];
4003        } elseif ($userLocale != $engLanguage) {
4004            if (isset($glTextbookCategory[$engLanguage]['gl_name']) && $glTextbookCategory[$engLanguage]['gl_name']) {
4005                $textbookCategoryNameEng = $glTextbookCategory[$engLanguage]['gl_name'];
4006            }
4007        }
4008        
4009        $lastLessonTextBookCatgName = isset($userLastLesson['TextbookCategory']['name']) ? $userLastLesson['TextbookCategory']['name'] : null;
4010        $lastLessonTextBookSubCtgName = isset($userLastLesson['TextbookSubCategory']['name']) ? $userLastLesson['TextbookSubCategory']['name'] : null;
4011        $lastLessonTextbookChapterName = isset($userLastLesson['Textbook']['name']) ? $userLastLesson['Textbook']['name'] : null;
4012
4013        // - NC-6756 HOT FIX
4014        if (isset($userLastLesson['GlobalTextbookCategory']['gl_name']) && $userLastLesson['GlobalTextbookCategory']['gl_name']) {
4015            $lastLessonTextBookCatgName = $userLastLesson['GlobalTextbookCategory']['gl_name'];
4016        } elseif (isset($userLastLesson['TextbookCategory']['english_name']) && $userLastLesson['TextbookCategory']['english_name']) {
4017            $lastLessonTextBookCatgName = $userLastLesson['TextbookCategory']['english_name'];
4018        }
4019        if (isset($userLastLesson['GlobalTextbookSubCategory']['gl_name']) && $userLastLesson['GlobalTextbookSubCategory']['gl_name']) {
4020            $lastLessonTextBookSubCtgName = $userLastLesson['GlobalTextbookSubCategory']['gl_name'];
4021        } elseif (isset($userLastLesson['TextbookSubCategory']['english_name']) && $userLastLesson['TextbookSubCategory']['english_name']) {
4022            $lastLessonTextBookSubCtgName = $userLastLesson['TextbookSubCategory']['english_name'];
4023        }
4024        if (isset($userLastLesson['GlobalTextbook']['gl_name']) && $userLastLesson['GlobalTextbook']['gl_name']) {
4025            $lastLessonTextbookChapterName = $userLastLesson['GlobalTextbook']['gl_name'];
4026        } elseif (isset($userLastLesson['Textbook']['name_eng']) && $userLastLesson['Textbook']['name_eng']) {
4027            $lastLessonTextbookChapterName = $userLastLesson['Textbook']['name_eng'];
4028        }
4029
4030        // Current lesson or todays lesson
4031        if (isset($lessonOnair['GlobalTextbookCategory']['gl_name']) && $lessonOnair['GlobalTextbookCategory']['gl_name']) {
4032            $textbookCategoryName = $lessonOnair['GlobalTextbookCategory']['gl_name'];
4033        } elseif (isset($lessonOnair['TextbookCategory']['english_name']) && $lessonOnair['TextbookCategory']['english_name']) {
4034            $textbookCategoryName = $lessonOnair['TextbookCategory']['english_name'];
4035        }
4036        if (isset($lessonOnair['GlobalTextbookSubCategory']['gl_name']) && $lessonOnair['GlobalTextbookSubCategory']['gl_name']) {
4037            $textbookClassName = $lessonOnair['GlobalTextbookSubCategory']['gl_name'];
4038        } elseif (isset($lessonOnair['TextbookSubCategory']['english_name']) && $lessonOnair['TextbookSubCategory']['english_name']) {
4039            $textbookClassName = $lessonOnair['TextbookSubCategory']['english_name'];
4040        }
4041        if (isset($lessonOnair['GlobalTextbook']['gl_name']) && $lessonOnair['GlobalTextbook']['gl_name']) {
4042            $textBookChapterName = $lessonOnair['GlobalTextbook']['gl_name'];
4043        } elseif (isset($lessonOnair['Textbook']['name_eng']) && $lessonOnair['Textbook']['name_eng']) {
4044            $textBookChapterName = $lessonOnair['Textbook']['name_eng'];
4045        }
4046
4047        // check for main topic
4048        if ( isset($userLastLesson['Textbook']['main_topic_id']) && $userLastLesson['Textbook']['main_topic_id'] ) {
4049            $lastTextbookMainTopicName = ClassRegistry::init('Textbook')->getTextbookMainTopicName($userLastLesson['Textbook']['id'],Configure::read('default.teacher_timezone_id'));
4050            if ($lastTextbookMainTopicName) { 
4051                $lastLessonTextBookSubCtgName = $lastTextbookMainTopicName; 
4052            }
4053        }
4054        
4055        // check for main topic
4056        if ( isset($lessonOnair['Textbook']['main_topic_id']) && $lessonOnair['Textbook']['main_topic_id'] ) {
4057            $langId = ClassRegistry::init('CountryCode')->getUserLanguageId($userLocale);
4058            $textbookMainTopicName = ClassRegistry::init('Textbook')->getTextbookMainTopicName($lessonOnair['Textbook']['id'],$langId);
4059            $textbookMainTopicNameEn = ClassRegistry::init('Textbook')->getTextbookMainTopicName($lessonOnair['Textbook']['id'],Configure::read('default.teacher_timezone_id'));
4060            if ($textbookMainTopicName) { $textbookClassName = $textbookMainTopicName; }
4061            if ($textbookMainTopicNameEn) { $textbookClassNameEng = $textbookMainTopicNameEn; }
4062        }
4063
4064        $ongoinTextbookCategoryType = isset($lessonOnair['TextbookCategory']['textbook_category_type']) ? $lessonOnair['TextbookCategory']['textbook_category_type'] : null;
4065        $ongoinTextbookConnectId = isset($lessonOnair['LessonOnair']['connect_id']) ? $lessonOnair['LessonOnair']['connect_id'] : null;
4066
4067        $viewVars['last_lesson_date'] = isset($userLastLesson['LessonOnairsLog']['end_time']) ? date('m/d/Y', strtotime($userLastLesson['LessonOnairsLog']['end_time'])): 'None';
4068        $viewVars['last_lesson_textbook'] = ($lastLessonTextBookSubCtgName || $lastLessonTextbookChapterName) ? $lastLessonTextBookSubCtgName . ' - ' . $lastLessonTextbookChapterName : 'None';
4069        
4070        $viewVars['last_category_textbook'] = ($lastLessonTextBookCatgName) ? $lastLessonTextBookCatgName : 'None';
4071        $viewVars['last_subcategory_textbook'] = ($lastLessonTextBookSubCtgName ) ? $lastLessonTextBookSubCtgName : 'None';
4072        $viewVars['last_textbook'] = ($lastLessonTextbookChapterName) ? $orderLastLesson . $lastLessonTextbookChapterName : 'None';
4073
4074        $viewVars['category_textbook'] = $textbookCategoryName;
4075        $viewVars['subcategory_textbook'] = $textbookClassName;
4076        $viewVars['textbook'] = $order . $textBookChapterName;
4077
4078        $viewVars['lessonRequestTime'] = isset($lessonOnair['LessonOnair']['requested_lesson_time']) ? (floor($lessonOnair['LessonOnair']['requested_lesson_time']/60)) : 25;
4079        $viewVars['lessonRequestSelfIntroduction'] = isset($lessonOnair['LessonRequest']['self_introduction']) ? $lessonOnair['LessonRequest']['self_introduction'] : 1;
4080        $viewVars['lessonRequestIncorrectGrammar'] = isset($lessonOnair['LessonRequest']['incorrect_grammar']) ? $lessonOnair['LessonRequest']['incorrect_grammar'] : 1;
4081        $viewVars['lessonRequestOther'] = isset($lessonOnair['LessonRequest']['other_request']) ? $lessonOnair['LessonRequest']['other_request'] : 'none';
4082        $viewVars['textBookName'] = ($textBookChapterName || $textbookClassName) ? $textbookClassName . ' - '. $order . $textBookChapterName : null;
4083        $viewVars['textBookNameEng'] = ($textBookChapterNameEng || $textbookClassNameEng) ? $textbookClassNameEng .' - '. $textBookChapterNameEng: null;
4084        // check finish lesson
4085        $viewVars['lessonFinish'] = $lessonOnair['LessonRequest']['lesson_finish'];
4086
4087        // set false when textbook is callang or counseling
4088        $viewVars['lessonRequestinfoVisible'] = true;
4089        if (!$ongoinTextbookConnectId || in_array($ongoinTextbookCategoryType, Configure::read('isCallan')) || ($ongoinTextbookConnectId == Configure::read('counselor.connect_id'))) {
4090            $viewVars['lessonRequestinfoVisible'] = false;
4091        }
4092        // set the lesson type
4093        $viewVars['lessonType'] = $lessonOnair['LessonOnair']['lesson_type'];
4094
4095        // set the student Id
4096        $viewVars['studentId'] = $lessonOnair['LessonOnair']['user_id'];
4097        $viewVars['studentLessonLocalizeDir'] = $userLocale;
4098
4099        //NJ-60220
4100        $userLang = $this->User->getNativeLanguage($lessonOnair['LessonOnair']['user_id'] ?? 0);
4101        $viewVars['user_lang'] = $userLang;
4102
4103        // view vars
4104        $viewVars['studentUA'] = $lessonOnair['LessonOnair']['user_agent'];
4105
4106        // count student lesson
4107        $viewVars['studentLessonCount'] = isset($userLastLesson[0]['count_lesson']) ? $userLastLesson[0]['count_lesson'] : 0;
4108
4109        //get device type
4110        $viewVars['studentDeviceType'] = $studentDeviceType;
4111
4112        //get chocotto flg
4113        $viewVars['chocotto_camp_lesson_flg'] = $lessonOnair['LessonOnair']['chocotto_camp_lesson_flg'];
4114
4115        if ($lessonOnair['LessonOnair']['lesson_type'] == Configure::read('lesson.type.reservation') && isset($lessonOnair['LessonOnair']['user_id'])) {
4116            $viewVars['reserveTextbook'] = array(
4117                'category_id' => $lessonOnair['TextbookCategory']['id'],
4118                'subcategory_id' => $lessonOnair['TextbookSubCategory']['id'],
4119                'connect_id' => $ongoinTextbookConnectId,
4120                'chapter_id' => $lessonOnair['Textbook']['chapter_id'],
4121                'textbook_category_type' => $ongoinTextbookCategoryType,
4122                'is_callan' => in_array($ongoinTextbookCategoryType, Configure::read('isCallan')),
4123                'callan_progress' => ( in_array($ongoinTextbookCategoryType, Configure::read('callan_textbook_type')) ) ? $this->LessonOnairsLog->callanProgressMessageArr( array( "user_id" => $lessonOnair['LessonOnair']['user_id'], "textbook_category_type" => $ongoinTextbookCategoryType ) ) : array(),
4124                'audio_acquisition_flg' => $lessonOnair['TextbookCategory']['audio_acquisition_flg']
4125            );
4126        }
4127         
4128        $this->User->virtualFields['teacher_camera_toggle_flg'] = "(SELECT teachers.camera_toggle_flg FROM teachers WHERE teachers.id = ". $this->Auth->user('id') ." LIMIT 1)";
4129        // get the user's information
4130        $student = $this->User->find('first', array(
4131            'fields' => array(
4132                'User.nickname',
4133                'User.image_url',
4134                'User.id',
4135                'User.studysapuri_id',
4136                'User.callan_level_check',
4137                'User.nationality_id',
4138                'User.nationality_show_flg',
4139                'User.profile_image',
4140                'User.camera_toggle_flg',
4141                'User.corporate_id',
4142                'teacher_camera_toggle_flg'
4143            ),
4144            'conditions' => array(
4145                'User.id' => $lessonOnair['LessonOnair']['user_id'],
4146            ),
4147            'recursive' => -1
4148        ));
4149
4150        // check if student exists
4151        if ($student) {
4152            // get student information
4153            $studentInformation = new UserTable($student['User']);
4154
4155            // set student information
4156            $viewVars['studentImage'] = $studentInformation->getImageUrl();
4157            $viewVars['studentName'] = $studentInformation->nickname;
4158            $viewVars['callan_level_check'] = $studentInformation->callan_level_check;
4159            $viewVars['studentStudySapuriId'] = $studentInformation->studysapuri_id;
4160
4161            $corporateIdsDisabledFileUpload = Configure::read('corporate_ids_disabled_file_upload');
4162            $inCorporateTextbookControlFlg = in_array($studentInformation->corporate_id, $corporateIdsDisabledFileUpload) ? 1 : 0;
4163            $viewVars['corporateIdsDisabledFileUpload'] = $inCorporateTextbookControlFlg;
4164
4165            $nationalityLists = $this->Nationality->nationalityOptions();
4166            $studentNationality = (isset($nationalityLists[$studentInformation->nationality_id]) && $nationalityLists[$studentInformation->nationality_id]) ? $nationalityLists[$studentInformation->nationality_id] : '';
4167            $viewVars['studentNationality'] = $this->Nationality->getFlagNameAndImg(array('nationality_name' => $studentNationality, 'nationality_show' => $studentInformation->nationality_show_flg));
4168
4169            // if chocotto
4170            if($isChocottoUser = $this->isUserChocottoCamp($studentInformation->getMembershipTypeIndex())) {
4171                $usersDetail = $this->UsersDetail->chocottoCampUserDetails($studentInformation->id);
4172                $viewVars['studentCameraToggleFlag'] = (isset($usersDetail) && !empty($usersDetail['chocotto_camp_camera_flg']) && $usersDetail['chocotto_camp_camera_flg']) ? 1 : 0;
4173            }
4174
4175        // else set default image
4176        } else {
4177            $viewVars['studentImage'] = '/user/img/no_profile_img.jpg';
4178        }
4179
4180        // lesson camera toggle settings
4181        if(!isset($isChocottoUser)) {
4182            $viewVars['studentCameraToggleFlag'] = !empty($student['User']['camera_toggle_flg']) ? 1 : 0;
4183        }
4184        $viewVars['teacherCameraToggleFlag'] = !empty($student['User']['teacher_camera_toggle_flg']) ? 1 : 0;
4185
4186        // Update Current lesson's camera toggle settings
4187        $this->LessonOnair->updateAll(
4188            array(
4189                "LessonOnair.student_camera_toggle_flg" => $viewVars['studentCameraToggleFlag'],
4190                "LessonOnair.teacher_camera_toggle_flg" => $viewVars['teacherCameraToggleFlag']
4191            ),
4192            array(
4193                "LessonOnair.chat_hash" => $chatHash,
4194                "LessonOnair.student_camera_toggle_flg IS NULL",
4195                "LessonOnair.teacher_camera_toggle_flg IS NULL"
4196            )
4197        );
4198
4199        // return json vars
4200        return json_encode(array('error' => false, 'content' => $viewVars));
4201    }
4202
4203    /**
4204     * @api {post} /teacher/api/saveLessonConnectionType saveLessonConnectionType()
4205     * @apiName saveLessonConnectionType
4206     * @apiGroup API
4207     * @apiDescription Save lesson connection type
4208     * @apiSampleRequest off
4209     * 
4210     * @apiBody {Object} config Configuration data.
4211     * @apiBody {Object} connectionStats Connection statistics.
4212     * 
4213     * @apiSuccess {Boolean} error The error status.
4214     * @apiSuccess {String} content The content.
4215     *
4216     * @apiErrorExample {json} Success-Response:
4217     *     {
4218     *       "error": false,
4219     *       "content": "saved"
4220     *     }
4221     *
4222     * @apiErrorExample {json} Error-Response:
4223     *     {
4224     *       "error": true,
4225     *       "content": "invalid_request"
4226     *     }
4227     * 
4228      * @apiErrorExample {js} Used in: AngularJS
4229      * Location: "webroot/js/recruitment/webrtcv2/connect.js    
4230     */
4231    /**
4232     * save lesson connection type
4233     * -> this will be called once a stream between a student and teacher are established
4234     */
4235    public function saveLessonConnectionType () {
4236        $this->autoRender = false;
4237
4238        //MARK: - disallow saving of bitrate
4239        //September 28, 2017
4240        die();
4241
4242        // only allow ajax requests
4243        if (!$this->request->is('ajax')) {
4244            return json_encode(array('error' => true, 'content' => 'invalid_request'));
4245        }
4246
4247        // save bitrate only for office teachers
4248        if (
4249            !$this->Auth->user() ||
4250            ($this->Auth->user() && $this->Auth->user('home_flg') == 1)
4251        ) {
4252            // show info
4253            return json_encode(array('error' => true, 'content' => 'invalid_teacher'));
4254        }
4255
4256        // set post data
4257        $post = $this->request->data;
4258
4259        // set connection types
4260        $connectionType = 0;
4261        $config = $post['config'];
4262        $connectionStats = $post['connectionStats'];
4263
4264        // load model
4265        $this->loadModel("LessonOnairsConnectionStat");
4266
4267        // save lessononairs conncetion stat information
4268        $this->LessonOnairsConnectionStat->saveLessonConnectionStats(array(
4269            'teacher_id' => $config['teacherID'],
4270            'workstation_id' => $config['workstationID'],
4271            'chat_hash' => $config['chatHash'],
4272            'remote_candidate_type' => json_encode($connectionStats['remote_candidate_type']),
4273            'local_candidate_type' => json_encode($connectionStats['local_candidate_type']),
4274            'bytes_sent' => $connectionStats['bytes_sent'],
4275            'bytes_sent_diff' => $connectionStats['bytes_sent_diff'],
4276            'bytes_received' => $connectionStats['bytes_received'],
4277            'bytes_received_diff' => $connectionStats['bytes_received_diff'],
4278            'actual_bytes_sent' => $connectionStats['actual_bytes_sent'],
4279        ));
4280
4281        // show info
4282        return json_encode(array('error' => false, 'content' => 'saved'));
4283    }
4284
4285    /**
4286     * @api {post} /teacher/api/loadPreviousChatMessages loadPreviousChatMessages()
4287     * @apiName loadPreviousChatMessages
4288     * @apiGroup API
4289     * @apiDescription Load previous chat messages
4290     * @apiSampleRequest off
4291     * 
4292     * @apiBody {String} chatHash The chat hash.
4293     *
4294     * @apiSuccess {Boolean} error The error status.
4295     * @apiSuccess {String} content The content.
4296     * 
4297     * @apiErrorExample {json} Success-Response:
4298     *     {
4299     *       "error": false,
4300     *       "content": [...]
4301     *     }
4302     *
4303     * @apiErrorExample {json} Error-Response:
4304     *     {
4305     *       "error": true,
4306     *       "content": "invalid_request"
4307     *     }
4308     * 
4309     * @apiErrorExample {js} Used in: WebRTC
4310      * Location: "webroot/js/webrtcv2/event.common.js"
4311      * Location: "webroot/js/recruitment/webrtcv2/event.common.js"
4312     */
4313    /**
4314     * load previous chat messages
4315     */
4316    public function loadPreviousChatMessages () {
4317        $this->autoRender = false;
4318
4319        // only allow ajax requests
4320        if (!$this->request->is('ajax')) {
4321            return json_encode(array('error' => true, 'content' => 'invalid_request'));
4322        }
4323
4324        // set post data
4325        $post = $this->request->data;
4326
4327        // check if has chat hash
4328        if (!isset($post['chatHash'])) {
4329            return json_encode(array('error' => true, 'content' => 'invalid_chat_hash'));
4330        }
4331
4332        // load the previous messages
4333        $this->ChatHistory->openDBReplica();
4334        $lessonMessages = $this->ChatHistory->loadPreviousChatMessages($post['chatHash']);
4335        $this->ChatHistory->closeDBReplica();
4336
4337        // if has no lesson messages
4338        if (!$lessonMessages) {
4339            return json_encode(array('error' => false, 'content' => 'no_chat_messages'));
4340        }
4341
4342        // return data
4343        return json_encode(array('error' => false, 'content' => $lessonMessages));
4344    }
4345
4346    /**
4347     * @api {get} /teacher/api/checkSlotHasReservation checkSlotHasReservation()
4348     * @apiName checkSlotHasReservation
4349     * @apiGroup API
4350     * @apiDescription Check if slot has reservation
4351     * @apiSampleRequest off
4352     * 
4353     * @apiBody {String} teacherId The ID of the teacher.
4354     * @apiBody {String} reservedTime The reserved time to check.
4355     *
4356     * @apiSuccess {Boolean} isReserved The reservation status.
4357     * 
4358     * @apiErrorExample {json} Success-Response:
4359     *     {
4360     *       "isReserved": 1
4361     *     }
4362     *
4363     * @apiErrorExample {json} Error-Response:
4364     *     {
4365     *       "isReserved": 0
4366     *     }
4367     * 
4368      * @apiErrorExample {js} Used in: AngularJS
4369     * Location: "webroot/js/ng/controller/schedule.js"
4370     */
4371    /*
4372    * check if slot has reservation
4373    * @param  array $data GET data from ajax
4374    * @return json data
4375    */
4376    public function checkSlotHasReservation() {
4377        $this->autoRender = false;
4378        $this->layout = false;
4379        $isReserved = 0;
4380        $get = $this->params->query;
4381
4382        if (isset($get['teacherId']) && $get['teacherId'] && isset($get['reservedTime']) && $get['reservedTime']) {
4383            $teacherId = $get['teacherId'];
4384            $reservedTime = date('Y-m-d H:i:s', strtotime($get['reservedTime']));
4385            $isReserved = $this->LessonSchedule->isReservedFromTeacherIdAndReserveTime($teacherId, $reservedTime, null);
4386        }
4387
4388        return json_encode($isReserved);
4389    }
4390
4391    /**
4392     * @api {get} /teacher/api/reviveSessionChecker reviveSessionChecker()
4393     * @apiName reviveSessionChecker
4394     * @apiGroup API
4395     * @apiDescription Revive session if dead
4396     * @apiSampleRequest off
4397     * 
4398     * @apiBody {String} teacherID The ID of the teacher.
4399     * @apiBody {String} workstationID The ID of the workstation.
4400     * @apiBody {String} workstationName The name of the workstation.
4401     * @apiBody {String} status The status of the teacher.
4402     * @apiBody {String} remarks The remarks for the status.
4403     *
4404     * @apiSuccess {Boolean} error The error status.
4405     * @apiSuccess {String} content The content.
4406     * 
4407     * @apiErrorExample {json} Success-Response:
4408     *     {
4409     *       "error": false,
4410     *       "content": "session_recovered"
4411     *     }
4412     *
4413     * @apiErrorExample {json} Error-Response:
4414     *     {
4415     *       "error": true,
4416     *       "content": "Error message"
4417     *     }
4418     * 
4419      * @apiErrorExample {js} Used in: AngularJS
4420      * Location: "webroot/js/recruitment/webrtcv2/event.common.js"
4421      * Location: "webroot/js/webrtcv2/event.common.js"
4422     */
4423    /**
4424     * revive session if dead
4425     */
4426    public function reviveSessionChecker () {
4427        $this->autoRender = false;
4428        $this->autoLayout = false;
4429        
4430        // get $_GET data
4431        $getData = $this->request->query;
4432
4433        // check if teacherID params exists
4434        if (
4435            !isset($getData['teacherID']) ||
4436            !isset($getData['workstationID']) ||
4437            !isset($getData['workstationName']) ||
4438            !isset($getData['status']) ||
4439            !isset($getData['remarks'])
4440        ) {
4441            return json_encode(array('error' => true, 'content' => 'empty_teacher_id'));
4442        }
4443        
4444        // check if session still exists
4445        if ($this->Auth->user('id')) {
4446            // get last teacher status
4447            $this->TeacherStatus->openDBReplica();
4448            $teacherStatus = $this->TeacherStatus->find('first', array(
4449                'conditions' => array(
4450                    'TeacherStatus.teacher_id' => $getData['teacherID']
4451                ),
4452                'recursive' => -1
4453            ));
4454            $this->TeacherStatus->closeDBReplica();
4455            
4456            $this->LessonOnair->openDBReplica();
4457            $isDuringLesson = $this->LessonOnair->find('first', array(
4458                'conditions' => array(
4459                    'LessonOnair.teacher_id' => $getData['teacherID'],
4460                    'LessonOnair.status' => array(2, 3)
4461                ),
4462                'recursive' => -1
4463            ));
4464            $this->LessonOnair->closeDBReplica();
4465
4466            if (
4467                    $teacherStatus &&
4468                    isset($teacherStatus['TeacherStatus']) &&
4469                    $teacherStatus['TeacherStatus']['status'] == 6
4470                ) {    
4471                // - if loggedout for an hour
4472                if ($teacherStatus['TeacherStatus']['remarks2'] == "admin_force_teacher_logout_forceLogout") {
4473                    return json_encode(array('error' => true, 'content' => 'admin_force_teacher_logged_out'));                
4474                // - default -> do nothing
4475                } else {
4476                    return json_encode(array('error' => true, 'content' => 'teacher_logged_out', 'is_during_lesson' => $isDuringLesson ? 1 : 0));
4477                }
4478            } else {
4479                // retrieve teacher status
4480                return json_encode(array('error' => true, 'content' => 'session_exists'));
4481            }
4482        }
4483
4484        // get last teacher status
4485        $this->TeacherStatus->openDBReplica();
4486        $teacherStatus = $this->TeacherStatus->find('first', array(
4487            'conditions' => array(
4488                'TeacherStatus.teacher_id' => $getData['teacherID']
4489            ),
4490            'recursive' => -1
4491        ));
4492        $this->TeacherStatus->closeDBReplica();
4493
4494        // *if Workstation ID is empty recover worktation from current status
4495        if ($teacherStatus && empty($getData['workstationID'])) {
4496            $this->log(__METHOD__ . ': '. date('Y-m-d H:i:s'). ' Teacher: '.$getData['teacherID'].' empty workstation', 'debug');
4497            $workstationId = $teacherStatus['TeacherStatus']['workstation_id'];
4498
4499            $con = array(
4500                    'fields' => array('Workstation.name'),
4501                    'conditions' => array('Workstation.id' => $workstationId),
4502                    'recursive' => -1
4503                  );
4504
4505            $getStatus = $this->Workstation->find('first', $con);
4506            if ($getStatus) {
4507                $getData['workstationID'] = $workstationId;
4508                $getData['workstationName'] = $getStatus['Workstation']['name'];
4509            }
4510        }
4511
4512        // check if last status is logged out
4513        if (
4514            $this->Auth->user('id') &&
4515            $teacherStatus &&
4516            isset($teacherStatus['TeacherStatus']) &&
4517            $teacherStatus['TeacherStatus']['status'] == 6
4518        ) {    
4519            // - if loggedout for an hour
4520            if ($teacherStatus['TeacherStatus']['remarks2'] == Configure::read('remarks2.1hour_not_standby')) {
4521                $this->Auth->logout();
4522                return json_encode(array('error' => true, 'content' => Configure::read('remarks2.1hour_not_standby')));            
4523            }else {
4524                return json_encode(array('error' => true, 'content' => 'teacher_logged_out'));
4525            }
4526        }
4527        else{
4528            return json_encode(array('error' => true, 'content' => 'session_exists'));
4529        }
4530
4531        // get teacher information
4532        $this->Teacher->openDBReplica();
4533        $teacherInfo = $this->Teacher->find('first', array(
4534            'conditions' => array(
4535                'Teacher.id' => $getData['teacherID'],
4536                'Teacher.status' => 1
4537            ),
4538            'recursive' => -1
4539        ));
4540        $this->Teacher->closeDBReplica();
4541
4542        // check if teacher information exists
4543        if (!$teacherInfo) {
4544            return json_encode(array('error' => true, 'content' => 'empty_teacher'));
4545        }
4546
4547        // set teacher info
4548        $teacherInfo = $teacherInfo['Teacher'];
4549
4550        // set teacher type
4551        $teacherInfo['type'] = 'teacher';
4552        $this->Auth->login($teacherInfo);
4553
4554        // set teacher status
4555        $data = array(
4556            'teacher_id' => $getData['teacherID'],
4557            'workstation_id' => $getData['workstationID'],
4558            'workstation_name' => $getData['workstationName'],
4559            'status' => $getData['status'],
4560            'remarks1' => $getData['remarks'],
4561            'remarks2' => "session_recovery"
4562        );
4563        $nowTeacherStatus = $getData['status'];
4564
4565        // check if last teacher status exists
4566        if (!$teacherStatus) {
4567            // check if others or mealbreak
4568            if ($nowTeacherStatus == 6 || $nowTeacherStatus == 7) {
4569                $data['status'] = 4;
4570            }
4571
4572            if(isset($data['status']) && $data['status'] == 2) {
4573                $this->log(__METHOD__ . '[TEACHER STATUS STANDBY] teacher_id -> ' . json_encode($getData['teacherID']), 'error');
4574            }
4575
4576            // save teacher status
4577            TeacherStatusLogTable::save($data);
4578            TeacherStatusTable::save($data);
4579        }
4580
4581        // set teacher session
4582        myTools::init_session(true);
4583        $this->Session->write('Teacher.status', $nowTeacherStatus);
4584        $this->Session->write('Teacher.remarks', $data['remarks1']);
4585        $this->Session->write('Teacher.remarks', $getData['remarks2']);
4586        $this->Session->write('Teacher.workstationId', $getData['workstationID']);
4587        $this->Session->write('Teacher.workstationName', $getData['workstationName']);
4588
4589        myTools::init_session();
4590
4591        //assign memcache as backup of workstation
4592        $memcached = new myMemcached();
4593        $memcached->set(array('key' => 'Teacher.workstationId.'.$getData['teacherID'], 'value' => $getData['workstationID']));
4594        $memcached->set(array('key' => 'Teacher.workstationName.'.$getData['teacherID'], 'value' => $getData['workstationName']));
4595
4596        // session was recovered
4597        return json_encode(array('error' => false, 'content' => 'session_recovered'));
4598    }
4599
4600    /**
4601     * @api {post} /teacher/api/syncScheduleFromAccounting syncScheduleFromAccounting()
4602     * @apiName syncScheduleFromAccounting
4603     * @apiGroup API
4604     * @apiDescription used to sync teacher schedules.
4605     * @apiSampleRequest off
4606     * 
4607     * @apiSuccess {Boolean} result The result of the sync.
4608     * 
4609     * @apiErrorExample {json} Success-Response:
4610     *     {
4611     *       "result": true
4612     *     }
4613     *
4614     * @apiErrorExample {json} Error-Response:
4615     *     {
4616     *       "result": false
4617     *     }
4618     * 
4619      * @apiErrorExample {js} Used in: AngularJS
4620     * Location: "webroot/js/ng/controller/schedule.js"
4621     */
4622    /**
4623    * Call the function that sync teacher schedules
4624    */
4625    public function syncScheduleFromAccounting() {
4626        $this->autoRender = false;
4627        $result = false;
4628        if ($this->request->is('post') && !$this->homeBase) {
4629            $result = $this->ShiftWorkOn->syncShiftFromAccounting($this->Auth->user('id'));
4630        }
4631        return json_encode(array('result' => $result));
4632    }
4633
4634    /**
4635     * @api {get} /teacher/api/getUnlessonReservationAndLessonFinishTime getUnlessonReservationAndLessonFinishTime()
4636     * @apiName getUnlessonReservationAndLessonFinishTime
4637     * @apiGroup API
4638     * @apiDescription used to get the unlesson reservation and lesson finish time.
4639     * @apiSampleRequest off
4640     * 
4641     * @apiSuccess {Boolean} reservation The reservation status.
4642     * @apiSuccess {Boolean} tosUser The TOS user status.
4643     * @apiSuccess {Boolean} finishUnlessonReservation The finish unlesson reservation status.
4644     * @apiSuccess {Integer} reservationId The reservation ID.
4645     * @apiSuccess {Integer} timeoutDuration The timeout duration.
4646     * 
4647     * @apiErrorExample {json} Success-Response:
4648     *     {
4649     *       "reservation": true,
4650     *       "tosUser": true,
4651     *       "finishUnlessonReservation": true,
4652     *       "reservationId": 123,
4653     *       "timeoutDuration": 1500000
4654     *     }
4655     *
4656     * @apiErrorExample {json} Error-Response:
4657     *     {
4658     *       "reservation": false,
4659     *       "tosUser": false,
4660     *       "finishUnlessonReservation": false
4661     *     }
4662     * 
4663      * @apiErrorExample {js} Used in: AngularJS
4664      * Location: "webroot/js/ng/app.js"
4665     */
4666    public function getUnlessonReservationAndLessonFinishTime() {
4667        $this->autoRender = false;
4668        $teacherId = $this->Auth->user('id');
4669        $result = [
4670            'reservation' => false,
4671            'tosUser' => false,
4672            'finishUnlessonReservation' => false
4673        ];
4674
4675        // check if booked
4676        $padding = Configure::read('reservation_padding');
4677        $lessonTime = 25;
4678        $lessonConfig = Configure::read('lesson');
4679        $status = $lessonConfig['status'];
4680        $procDate = date('Y-m-d H:i:s');
4681        $conditions = array(
4682            'LessonSchedule.teacher_id' => $teacherId,
4683            'LessonSchedule.status' => 1,
4684            'DATE_FORMAT(lesson_time + interval ' . $lessonTime . ' minute,"%Y-%m-%d %H:%i") >=' => date('Y-m-d H:i', strtotime($procDate)),
4685            'DATE_FORMAT(lesson_time - interval ' .$padding . ' minute,"%Y-%m-%d %H:%i") <=' => date('Y-m-d H:i', strtotime($procDate))
4686        );
4687
4688        // get unlesson reservation
4689        $this->LessonSchedule->openDBReplica();
4690        $data = $this->LessonSchedule->find('first', array(
4691            'fields' => array(
4692                'LessonSchedule.lesson_time',
4693                'LessonSchedule.id',
4694                'LessonSchedule.user_id'
4695            ),
4696            'conditions' => $conditions,
4697            'recursive' => -1
4698        ));
4699        $this->LessonSchedule->closeDBReplica();
4700
4701        if (!empty($data)) {
4702            $ls = $data['LessonSchedule'];
4703            $result['tosUser'] = $this->UsersExtend->checkIfTosUser($ls['user_id']);
4704
4705            if ($result['tosUser']) {
4706                $result['reservation'] = true;
4707                # add 25 minutes
4708                $finishTime = strtotime('+25 minutes '.$ls['lesson_time']);
4709                $currentTime = time();
4710
4711                # check if current date is greater than cancel time.
4712                if ($currentTime <= $finishTime) {
4713                    $result['finishUnlessonReservation'] = true;
4714                    $finishTime = $finishTime - $currentTime;
4715
4716                    $result['reservationId'] = $ls['id'];
4717                    $result['timeoutDuration'] =  $finishTime * 1000; # 1000 ms = 1 second
4718                }
4719            }
4720        }
4721
4722        return json_encode($result);
4723    }
4724
4725    /**
4726     * @api {post} /teacher/api/finishUnlessonReservation finishUnlessonReservation()
4727     * @apiName finishUnlessonReservation
4728     * @apiGroup API
4729     * @apiDescription used to finish an unlesson reservation.
4730     * @apiSampleRequest off
4731     * 
4732     * @apiBody {String} reservation_id The ID of the reservation.
4733     *
4734     * @apiSuccess {Boolean} lessonFinish The lesson finish status.
4735     * @apiSuccess {Boolean} canStanbyForReservation The standby for reservation status.
4736     * 
4737     * @apiErrorExample {json} Success-Response:
4738     *     {
4739     *       "lessonFinish": true,
4740     *       "canStanbyForReservation": true
4741     *     }
4742     *
4743      * @apiErrorExample {js} Used in: AngularJS
4744      * Location: "webroot/js/ng/app.js"
4745     */
4746    public function finishUnlessonReservation() {
4747        $this->autoRender = false;
4748        $ret = ['lessonFinish' => false];
4749
4750        if ($this->request->is('post')) {
4751            $post = $this->request->data;
4752            $lsId = $post['reservation_id'];
4753
4754            $this->LessonSchedule->virtualFields = ['onair_id' => "SELECT lo.id FROM lesson_onairs lo where lo.teacher_id = LessonSchedule.teacher_id and lo.status = 2 LIMIT 1"];
4755            $data = $this->LessonSchedule->find('first', [
4756                'fields' => [
4757                    'LessonSchedule.id',
4758                    'LessonSchedule.user_id',
4759                    'LessonSchedule.teacher_id',
4760                    'LessonSchedule.connect_id',
4761                    'LessonSchedule.payment_plan_id',
4762                    'LessonSchedule.lesson_time',
4763                    'LessonSchedule.onair_id'
4764                ],
4765                'joins' => [
4766                    [
4767                        'table' => 'users_extend',
4768                        'alias' => 'UsersExtend',
4769                        'type' => 'INNER',
4770                        'conditions' => 'UsersExtend.user_id = LessonSchedule.user_id'
4771                    ]
4772                ],
4773                'conditions' => ['LessonSchedule.id' => $lsId]
4774            ]);
4775
4776            if ($data) {
4777                $ls = $data['LessonSchedule'];
4778                $endTime = date('Y-m-d H:i:s', strtotime($ls['lesson_time']) + 60 * 26);
4779
4780                // set params
4781                $param = array(
4782                    'method' => APP_DIR.' | '.__METHOD__,
4783                    'action_by' => 1,
4784                    'student_lesson_localize_dir' => Configure::read('default.user_language'),
4785                    'tos_special_reserved_lesson_flg' => true,
4786                    'tos_special_reserved_lesson_data' => [
4787                        'status' => 3, // lesson
4788                        'user_id' => $ls['user_id'],
4789                        'payment_plan_id' => $ls['payment_plan_id'],
4790                        'lesson_type' => 2, // lesson
4791                        'connect_id' => $ls['connect_id'],
4792                        'lesson_schedule_id' => $ls['id'],
4793                        'start_time' => $ls['lesson_time'],
4794                        'end_time' => $endTime
4795                    ]
4796                );
4797
4798                if (LessonOnairTable::delete($ls['onair_id'], $param)) {
4799                    $ret['canStanbyForReservation'] = true;
4800                    $ret['lessonFinish'] = true;
4801
4802                    if ($this->homeBase) {
4803                        // get reservation data
4804                        $this->LessonSchedule->openDBReplica();
4805                        $nextReservationData = $this->LessonSchedule->find('first', array(
4806                            'fields' => array('lesson_time'),
4807                            'conditions' => array(
4808                                'teacher_id' => $lsId,
4809                                'lesson_time >' => $endTime
4810                            ),
4811                            'order' => 'lesson_time ASC',
4812                            'recursive' => -1
4813                        ));
4814
4815                        $this->LessonSchedule->closeDBReplica();
4816
4817                        if (!$nextReservationData) {
4818                            $ret['canStanbyForReservation'] = false;
4819                        }
4820                    }
4821                }
4822            }
4823        }
4824
4825        return json_encode($ret);
4826    }
4827
4828    /**
4829     * @api {get} /teacher/api/getReservationAndCancelTime getReservationAndCancelTime()
4830     * @apiName getReservationAndCancelTime
4831     * @apiGroup API
4832     * @apiDescription used to check if a lesson is reserved and get the time to show the special modal.
4833     * @apiSampleRequest off
4834     * 
4835     * @apiSuccess {Boolean} reservation The reservation status.
4836     * @apiSuccess {Integer} userId The user ID.
4837     * @apiSuccess {String} lesson_time The lesson time.
4838     * @apiSuccess {Boolean} tosUser The TOS user status.
4839     * @apiSuccess {Integer} specialTimeoutDuration The special timeout duration.
4840     * @apiSuccess {String} custom_lesson_time The custom lesson time.
4841     * 
4842     * 
4843     * @apiErrorExample {json} Success-Response:
4844     *     {
4845     *       "reservation": true,
4846     *       "userId": 123,
4847     *       "lesson_time": "2023-10-01 10:00:00",
4848     *       "tosUser": true,
4849     *       "specialTimeoutDuration": 300,
4850     *       "custom_lesson_time": "20231001100000"
4851     *     }
4852     *
4853     * @apiErrorExample {json} Error-Response:
4854     *     {
4855     *       "reservation": false,
4856     *       "tosUser": false,
4857     *       "specialTimeoutDuration": 0,
4858     *       "custom_lesson_time": ""
4859     *     }
4860     * 
4861      * @apiErrorExample {js} Used in: AngularJS
4862      * Location: "webroot/js/ng/controller/header.js"
4863      * Location: "webroot/js/ng/app.js"
4864     */
4865    /*
4866    * check if lesson reservation.
4867    * get the lesson_schedule id and cancelled time for the student will not come.
4868    * @return json data
4869    */
4870    public function getReservationAndCancelTime() {
4871        $this->autoRender = false;
4872        $teacherId = $this->Auth->user('id');
4873        $result = LessonOnairTable::getReservationAndCancelTime($teacherId);
4874
4875        $result['tosUser'] = $result['reservation'] ? $this->UsersExtend->checkIfTosUser($result['userId']) : false;
4876        $result['specialTimeoutDuration'] = ($result['reservation'] && $result['tosUser']) ? $this->getRLSShowModalTime($result) : 0;
4877        $result['custom_lesson_time'] = ($result['reservation'] && $result['tosUser']) ? date('YmdHis', strtotime($result['lesson_time'])) : '';
4878
4879        return json_encode($result);
4880    }
4881
4882    /**
4883     * Get the remaining time (show special modal if timeoutDuration is 0)
4884     * @param  array $params
4885     * @return int
4886     */
4887    private function getRLSShowModalTime($params) {
4888        $this->autoRender = false;
4889
4890        if (!isset($params['userId'])) {
4891            return false;
4892        }
4893
4894        if (!isset($params['lesson_time'])) {
4895            return false;
4896        }
4897
4898        // set variables
4899        $userId = $params['userId'];
4900        $lessonTime = $params['lesson_time'];
4901        $currentTime = time();
4902        $showModalTime = strtotime('+5 minutes ' . $lessonTime);
4903
4904        // check if current date is greater than or equal to show modal time.
4905        if ($currentTime >= $showModalTime) {
4906            $timeoutDuration = 0;
4907        } else {
4908            $timeoutDuration = $showModalTime - $currentTime;
4909            $timeoutDuration = $timeoutDuration * 1000; # 1000 ms = 1 second
4910        }
4911
4912        return $timeoutDuration;
4913    }
4914
4915    /**
4916     * @api {post} /teacher/api/cancelReservation cancelReservation()
4917     * @apiName cancelReservation
4918     * @apiGroup API
4919     * @apiDescription used to cancel a reservation.
4920     * @apiSampleRequest off
4921     * 
4922     * @apiBody {String} reservation_id The ID of the reservation.
4923     * @apiBody {String} lesson_time The time of the lesson.
4924     * @apiBody {String} [teacher_id] The ID of the teacher (optional).
4925     *
4926     * @apiSuccess {Boolean} success The success status.
4927     * @apiSuccess {Boolean} isCancelled The cancelled status.
4928     * 
4929     * @apiErrorExample {json} Success-Response:
4930     *     {
4931     *       "success": true
4932     *     }
4933     *
4934     * @apiErrorExample {json} Error-Response:
4935     *     {
4936     *       "success": false,
4937     *       "isCancelled": true
4938     *     }
4939     * 
4940      * @apiErrorExample {js} Used in: AngularJS
4941      * Location: "webroot/js/ng/app.js"
4942      * Location: "webroot/js/recruitment/ng/app.js"
4943     */
4944    /*
4945    * cancel reservation
4946    * @post data reservationId
4947    * @return json data
4948    */
4949    public function cancelReservation() {
4950        $this->autoRender = false;
4951        $result['success'] = false;
4952        if ($this->request->is('post')) {
4953            $data = $this->request->data;
4954            $reservationId = $this->request->data['reservation_id'];
4955            $lessonTime = $this->request->data['lesson_time'];
4956            $cancellationReason = isset($data['cancellation_reason']) ? trim($data['cancellation_reason']) : null;
4957
4958            $dateTime = $lessonTime.":00";
4959            $teacherId = isset($this->request->data['teacher_id']) ? $this->request->data['teacher_id'] : $this->Auth->user('id');
4960            $this->log(__METHOD__ . ': '. date('Y-m-d H:i:s'). ' -- perform late cancellation', 'debug');
4961
4962            // check if reservation was was already cancelled
4963            $reservationCancelled = $this->LessonScheduleCancel->find('first', array(
4964                'fields' => array('cancelled_date'),
4965                'conditions' => array(
4966                    'teacher_id' => $teacherId,
4967                    'lesson_time' => $lessonTime,
4968                    'reservation_id' => $reservationId
4969                ),
4970                'recursive' => -1
4971            ));
4972
4973            $this->log(__METHOD__.'line 3034 ' . json_encode([$reservationCancelled,$result]), 'debug');
4974
4975            if ($reservationCancelled) {
4976                $result['isCancelled'] = true;
4977                return json_encode($result);
4978            }
4979
4980            //NC-6512 check if teacher can cancel 5/10 minutes before the reservation time
4981            $teacherData = $this->Teacher->findById($teacherId);
4982            $cancelTime = strtotime('-5 minutes '.$dateTime);
4983            if (isset($teacherData['Teacher']['counseling_flg']) && $teacherData['Teacher']['counseling_flg']) {
4984                $cancelTime = strtotime('-10 minutes '.$dateTime);
4985            }
4986            
4987            $lateCancellation = strtotime('+6 minutes'.$dateTime);
4988
4989            $this->log(__METHOD__.'line 3043 ' . json_encode([$cancelTime,$lateCancellation,$result]), 'debug');
4990
4991            if ($cancelTime <= time() && $lateCancellation >= time()) {
4992                return json_encode($result);
4993            }
4994
4995            $isLateCancelation = time() >= $lateCancellation;
4996            $params = array(
4997                'reservationId' => $reservationId,
4998                'isTeacherCancelation' => true,
4999                'isLateCancelation' => $isLateCancelation,
5000                'cancelFrom' => 'teacher',
5001                'notHomeBased' => (!$this->homeBase) ? true : null,
5002                'cancellationReason' => $cancellationReason,
5003            );
5004            LessonScheduleTable::cancelReservationAndRefund($params);
5005
5006            $hasShiftWorkOn = $this->ShiftWorkOn->checkDataExist($teacherId, $lessonTime, true);
5007
5008            // set status to 0
5009            if ($hasShiftWorkOn) {
5010                $this->LessonScheduleCancel->openDBReplica();
5011                $cancelledSched = $this->LessonScheduleCancel->find('first', array(
5012                    'conditions' => array(
5013                        'reservation_id' => $reservationId,
5014                        'status' => Configure::read('lock_shift_cancellation_types')
5015                    ),
5016                    'recursive' => -1
5017                ));
5018                $this->LessonScheduleCancel->closeDBReplica();
5019
5020                $shiftWorkOnData = array('status' => 0, 'lock_flg' => 0);
5021                if ($cancelledSched) {
5022                    $shiftWorkOnData['lock_flg'] = 1;
5023                }
5024
5025                $this->ShiftWorkOn->read(array('status', 'lock_flg'), $hasShiftWorkOn['ShiftWorkOn']['id']);
5026                $this->ShiftWorkOn->set($shiftWorkOnData);
5027                $this->ShiftWorkOn->save();
5028            }
5029
5030            //-- lesson time push notification
5031            $lessonTimeNotifParams = array(
5032                'lesson_time'     => date('Y-m-d H:i:s', strtotime($lessonTime)),
5033                'teacher_id'     => $teacherId
5034            );
5035            ClassRegistry::init('FcmDeviceToken')->lessonSlotPushNotification($lessonTimeNotifParams);
5036            $result['success'] = true;
5037        }
5038        return json_encode($result);
5039    }
5040
5041    /**
5042     * @api {post} /teacher/api/getStudentDisconnectionTime getStudentDisconnectionTime()
5043     * @apiName getStudentDisconnectionTime
5044     * @apiGroup API
5045     * @apiDescription Get student's last disconnection time by chat_hash.
5046     * @apiSampleRequest off
5047     * 
5048     * @apiBody {String} chatHash The chat hash.
5049     * 
5050     * @apiSuccess {Integer} student_disconnection_time The student disconnection time.
5051     * @apiError {Boolean} error The error status.
5052     * @apiError {String} content The content.
5053     *
5054     * @apiErrorExample {json} Success-Response:
5055     *     {
5056     *       "student_disconnection_time": 30000
5057     *     }
5058     *
5059     * @apiErrorExample {json} Error-Response:
5060     *     {
5061     *       "error": true,
5062     *       "content": "invalid_request"
5063     *     }
5064     */
5065    /**
5066     * get student's last disconnection time by chat_hash
5067     */
5068    public function getStudentDisconnectionTime () {
5069        $this->autoRender = false;
5070
5071        // only allow ajax requests
5072        if (!$this->request->is('ajax')) {
5073            return json_encode(array('error' => true, 'content' => 'invalid_request'));
5074        }
5075
5076        // set post data
5077        $post = $this->request->data;
5078
5079        // check if chathash exists
5080        if (!isset($post['chatHash'])) {
5081            return json_encode(array('error' => true, 'content' => 'invalid_params'));
5082        }
5083
5084        // get vars
5085        $chatHash = $post['chatHash'];
5086
5087        // declare myMemcached
5088        $memcached = new myMemcached();
5089
5090        // get last student disconnection from memcached
5091        $studentDisconnectionTime = $memcached->get('DC_' . $chatHash);
5092
5093        // if has content
5094        if ($studentDisconnectionTime) {
5095            $studentDisconnectionTime = time() - $studentDisconnectionTime;
5096            $studentDisconnectionTime = Configure::read('lesson_heartbeat_config_v2.disconnection_timeout') - $studentDisconnectionTime;
5097            $studentDisconnectionTime = $studentDisconnectionTime <= 0 ? 0 : $studentDisconnectionTime;
5098
5099        // if has no memcached, use 60 seconds
5100        } else {
5101            $this->log("[STUDENT_DISCONNECTION] student has no disconnection time -> " . json_encode($post), "debug");
5102            return json_encode(array('error' => true, 'content' => 'no_memcached_disconnection_time'));
5103        }
5104
5105        // multiply by 1000
5106        $studentDisconnectionTime = $studentDisconnectionTime * 1000;
5107
5108        // return json encoded array
5109        return json_encode(array('student_disconnection_time' => $studentDisconnectionTime));
5110    }
5111
5112    /**
5113    * @api {get} /teacher/api/finishStatusChange finishStatusChange()
5114    * @apiName finishStatusChange
5115    * @apiGroup API
5116    * @apiSampleRequest off
5117    * @apiDescription used to denote that the teacher has finished changing status.
5118    *
5119    * @apiErrorExample {js} Used in: Elements
5120    * Location: "view/Elements/header.php"
5121    */
5122    /**
5123     * this will denote that the teacher has finished changing status
5124     */
5125    public function finishStatusChange () {
5126        $this->autoRender = false;
5127
5128        // check if has request data
5129        $getData = $this->request->data;
5130        $teacherId = $this->Auth->user('id');
5131
5132        // create random salt for debugging
5133        $randomSalt = substr((md5(uniqid(rand(),1))), 0, 6);
5134
5135        // debug
5136        $this->log(__METHOD__ . " LESTER | -> cleaning up status change! : " . $randomSalt . " | teacher_id : " . json_encode($getData), "debug");
5137
5138        // if has no authenticated user
5139        if (!$teacherId && isset($getData['teacherId'])) {
5140            $teacherId = $getData['teacherId'];
5141        }
5142
5143        // if has no teacher ID
5144        if (!$teacherId) {
5145            $this->log(__METHOD__ . " LESTER | -> unable to finish status change as session is dead! : " . $randomSalt . " | teacher_id : " . $teacherId, "debug");
5146            return;
5147        }
5148
5149        // log
5150        $this->log("[SHUTDOWN] deleting cookies -> " . json_encode($this->Cookie->read('TEACHER_STATUS_' . $teacherId)), "debug");
5151
5152        // delete cookie
5153        $this->Cookie->delete('TEACHER_STATUS_' . $teacherId);
5154        $this->Cookie->delete('TEACHER_THREE_MINUTES_BREAK_' . $teacherId);
5155
5156        // debug
5157        $this->log(__METHOD__ . " LESTER | -> cleaning up session, session: " . $this->Session->read('Teacher.threeMinOtherBreak') . "| cookie : " . json_encode($this->Cookie->read('TEACHER_STATUS_' . $teacherId)) . "| salt: " . $randomSalt . " | teacher_id : " . $teacherId, "debug");
5158    }
5159
5160    /**
5161     * @api {post} /teacher/api/saveImageLink saveImageLink()
5162     * @apiName saveImageLink
5163     * @apiGroup API
5164     * @apiDescription used to store the snap shot of the teacher, used in admin teacher monitor.
5165     * @apiSampleRequest off
5166     * 
5167     * @apiBody {String} image_url The URL of the image.
5168     * @apiBody {String} teacher_status The status of the teacher.
5169     *
5170     * @apiSuccess {String} result The result of the save.
5171     * 
5172     * @apiErrorExample {json} Success-Response:
5173     *     {
5174     *       "result": "saved"
5175     *     }
5176     */
5177    /**
5178    * this will store the snap shot of the teacher, used in admin teacher monitor
5179    */
5180    public function saveImageLink() {
5181        $this->autoRender = false;
5182        return false;
5183
5184        if (!$this->Auth->user('id')) {
5185            return false;
5186        }
5187
5188        $data = $this->request->data;
5189        $imageURL = isset($data['image_url']) ? $data['image_url'] : null;
5190        $teacherStatus = isset($data['teacher_status']) ? $data['teacher_status'] : null;
5191        $teacherId = $this->Auth->user('id');
5192
5193        if ($this->request->is('post')) {
5194            $this->TeacherMonitorImages->create();
5195            $this->TeacherMonitorImages->set(array(
5196                'teacher_id' => $teacherId,
5197                'image_link' => $imageURL,
5198                'teacher_status' => $teacherStatus,
5199            ));
5200            if ($this->TeacherMonitorImages->save()) {
5201                return json_encode(array('result' => 'saved'));
5202            }
5203        }
5204    }
5205
5206    /**
5207     * @api {post} /teacher/api/get3MinutesBreakRemainingTime get3MinutesBreakRemainingTime()
5208     * @apiName get3MinutesBreakRemainingTime
5209     * @apiGroup API
5210     * @apiDescription used to get the teacher 3 minutes break remaining time in seconds.
5211     * @apiSampleRequest off
5212     * 
5213     * @apiSuccess {Boolean} status The status of the request.
5214     * @apiSuccess {Integer} remainingTime The remaining time in seconds.
5215     * @apiSuccess {Boolean} shift_meal_break The shift meal break status.
5216     * @apiSuccess {String} avatar_phrase_html The avatar phrase HTML.
5217     * 
5218     * @apiErrorExample {json} Success-Response:
5219     *     {
5220     *       "status": true,
5221     *       "remainingTime": 180,
5222     *       "shift_meal_break": false,
5223     *       "avatar_phrase_html": "<tr>...</tr>"
5224     *     }
5225     *
5226     * @apiErrorExample {json} Error-Response:
5227     *     {
5228     *       "status": false,
5229     *       "error": "Error message"
5230     *     }
5231     * 
5232      * @apiErrorExample {js} Used in: AngularJS
5233      * Location: "webroot/js/ng/home.js"
5234      * Location: "webroot/js/ng/controller/home.js"
5235     */
5236    /**
5237     * get teacher 3 minutes break remaining time in seconds
5238     * return array json_encode $returnData
5239     */
5240    public function get3MinutesBreakRemainingTime() {
5241        $this->autoRender = false;
5242        $remainingTime = 0;
5243        $teacherId = $this->Auth->user('id');
5244
5245        // if not AJAX request
5246        if (!$this->request->is('ajax')) {
5247            $this->log("[3_MINUTES_BREAKTIME] request is not AJAX", "debug");
5248            throw new Exception("invalid_request_type");
5249        }
5250
5251        // check if teacherId is empty
5252        if (empty($teacherId)) {
5253            $this->log("[3_MINUTES_BREAKTIME] teacher id is empty", "debug");
5254            throw new Exception("teacher_id_empty");
5255        }
5256
5257        if ($this->request->is('ajax')) {
5258            // get 3 minutes break start time
5259            $threeMinOtherBreakStartTime = $this->Cookie->read('threeMinOtherBreakStartTime_'.$teacherId);
5260
5261            $threeMinOtherBreakCurrentTime = time();
5262            if (empty($threeMinOtherBreakStartTime)) {
5263
5264                $ts = $this->TeacherStatus->find('first', array(
5265                    'fields' => array(
5266                        'TeacherStatus.teacher_id',
5267                        'TeacherStatus.status',
5268                        'TeacherStatus.remarks1',
5269                        'TeacherStatus.remarks2',
5270                        'TeacherStatus.created'
5271                    ),
5272                    'conditions' => array(
5273                        'TeacherStatus.teacher_id' => (int)$teacherId
5274                    )
5275                ));
5276
5277                //get time
5278                if (date('i') >= 30) {
5279                    $startTime = date('Y-m-d H:00:00', strtotime('+30 minutes'));
5280                } else {
5281                    $startTime = date('Y-m-d H:30:00');
5282                }
5283                //check if teacher have next reservation
5284                $nextReservation = $this->LessonSchedule->find('count', array(
5285                        'conditions' => array(
5286                            'LessonSchedule.teacher_id' => $teacherId,
5287                            'LessonSchedule.status = 1',
5288                            'LessonSchedule.lesson_time' => $startTime
5289                        ),
5290                        'recursive' => -1
5291                ));
5292                $threeMinOtherBreakStartTime = $threeMinOtherBreakCurrentTime;
5293                //lesson prepare end time
5294                $endTime = strtotime($startTime . ' -1 minute');
5295                if ($nextReservation && ($threeMinOtherBreakCurrentTime + Configure::read('break_other_limit')) > $endTime) {
5296                    $threeMinOtherBreakStartTime = $endTime - Configure::read('break_other_limit');
5297                }
5298
5299                // set cookies
5300                $this->log(__METHOD__ . " Setting prepare other time : " . $threeMinOtherBreakStartTime, "debug");
5301                $this->Cookie->write('threeMinOtherBreakStartTime_'.$teacherId,  $threeMinOtherBreakStartTime, false);
5302            }
5303            $remainingTime = Configure::read('break_other_limit') - ($threeMinOtherBreakCurrentTime - $threeMinOtherBreakStartTime);
5304
5305            if ($remainingTime < 0) {
5306                $remainingTime = 0; // set to 0
5307            }
5308
5309            $mb = null;
5310
5311            // NC-5554 : get next meal break slot
5312            if (!$this->Auth->user('home_flg')) {
5313
5314                $lesson_time = date('Y-m-d H:00:00');
5315                if (date('i') >= 30) {
5316                    $lesson_time = date('Y-m-d H:30:00');
5317                }
5318
5319                // NC-5554 : Check the current slot time is meal break.
5320                $mb = $this->ShiftWorkMealBreak->useReplica()->find('first', array(
5321                    'fields' => array('ShiftWorkMealBreak.lesson_time'),
5322                    'conditions' => array(
5323                        'ShiftWorkMealBreak.teacher_id' => $this->Auth->user('id'),
5324                        'ShiftWorkMealBreak.lesson_time' => $lesson_time
5325                    )
5326                ));
5327            }
5328
5329            if ( $this->Auth->user('avatar_id') && in_array($this->Auth->user('avatar_id'), array_keys(Configure::read('avatar_teacher.id'))) ) {
5330                $onAirModel = 'LessonOnair';
5331                $teacherId = $this->Auth->user('id');
5332                $avatarIdList         = Configure::read('avatar_teacher.id');
5333                $avatarPhraseList    = Configure::read('avatar_teacher.phrases');
5334                $avatarName         = $avatarIdList[$this->Auth->user('avatar_id')];
5335                $avatarPhraseKeys     = array_keys($avatarPhraseList[$avatarName]);
5336                $showAvatarUsedPhrasesData = ['count' => [], 'phrases' => $avatarPhraseList[$avatarName]];
5337                $onAirLogsChatHash = $this->getCurrentChatHash($onAirModel, $teacherId);
5338                $chatHash = $this->Session->read('chatHash');
5339                $chatHash = isset($chatHash) ? $chatHash : $onAirLogsChatHash;
5340                //~ get avatar word phrase use count
5341                $this->loadModel('AvatarPhraseRecord');
5342                $this->AvatarPhraseRecord->openDBReplica();
5343                $getRecordedPhrase = $this->AvatarPhraseRecord->find('all', [
5344                    'fields' => [
5345                        'AvatarPhraseRecord.avatar_phrase_id',
5346                        'AvatarPhraseRecord.total'
5347                    ],
5348                    'conditions' => [
5349                        'AvatarPhraseRecord.avatar_phrase_id' => $avatarPhraseKeys,
5350                        'AvatarPhraseRecord.teacher_id' => $this->Auth->user('id'),
5351                        'AvatarPhraseRecord.chat_hash' => $chatHash
5352                    ],
5353                ]);
5354                $this->AvatarPhraseRecord->closeDBReplica();
5355
5356                foreach($getRecordedPhrase as $value) {
5357                    $showAvatarUsedPhrasesData['count'][$value['AvatarPhraseRecord']['avatar_phrase_id']] = $value['AvatarPhraseRecord']['total'];
5358                }
5359    
5360                $count = $showAvatarUsedPhrasesData['count'];
5361                $phrases = $showAvatarUsedPhrasesData['phrases'];
5362                $htmlModal = '';
5363
5364                foreach($phrases as $key => $data):
5365                    $total = isset($count[$key]) ? $count[$key] : 0;
5366                    $htmlModal .= '<tr class="'.($total <= 0 ? 'no_record' : 'has_record').'">';
5367                    $htmlModal .= '<td>' . $data['text'] . '</td>';
5368                    $htmlModal .= '<td>×</td>';
5369                    $htmlModal .= '<td>' . $total . '</td>';
5370                    $htmlModal .= '</tr>';
5371                endforeach;
5372            }
5373
5374            $returnData = array(
5375                'status' => true,
5376                'remainingTime' => $remainingTime,
5377                'shift_meal_break' => !empty($mb['ShiftWorkMealBreak']['lesson_time']) ? true : false,
5378                'avatar_phrase_html' => isset($htmlModal) ? $htmlModal : null
5379            );
5380            $this->Session->delete('chatHash');
5381        }
5382        return json_encode($returnData);
5383    }
5384
5385    /**
5386     * @api {get} /teacher/api/getImpendingAndOngoingReservation getImpendingAndOngoingReservation()
5387     * @apiName getImpendingAndOngoingReservation
5388     * @apiGroup API
5389     * @apiDescription used to check if there is an impending or ongoing reservation.
5390     * @apiSampleRequest off
5391     * 
5392      * @apiErrorExample {js} Used in: AngularJS
5393      * Location: "webroot/js/ng/controller/header.js"
5394     */
5395    public function getImpendingAndOngoingReservation() {
5396        $this->autoRender = false;
5397        return $this->LessonSchedule->checkImpendingAndOngoingReservation($this->Auth->user('id'));
5398    }
5399
5400    /**
5401     * @api {get} /teacher/api/deleteMemMinutesBreakRemainingTime deleteMemMinutesBreakRemainingTime()
5402     * @apiName deleteMemMinutesBreakRemainingTime
5403     * @apiGroup API
5404     * @apiDescription used to delete the teacher 3 minutes break remaining time.
5405     * @apiSampleRequest off
5406     * 
5407     * @apiSuccess {Boolean} status The status of the deletion.
5408     * @apiErrorExample {json} Success-Response:
5409     *     {
5410     *       "status": true
5411     *     }
5412     *
5413     * @apiErrorExample {json} Error-Response:
5414     *     {
5415     *       "status": false
5416     *     }
5417     */
5418    public function deleteMemMinutesBreakRemainingTime() {
5419        $this->autoRender = false;
5420        if ($this->request->is('get')) {
5421            $teacherId = $this->Auth->user('id');
5422            $threeMinOtherBreakStartTime = $this->Cookie->read('threeMinOtherBreakStartTime_'.$teacherId);
5423            if (!empty($threeMinOtherBreakStartTime)) {
5424                $this->Cookie->delete('threeMinOtherBreakStartTime_' . $teacherId);
5425                return json_encode(array('status' => true));
5426            }
5427            $this->log('hotfix_NC-3337: threeMinOtherBreakStartTime_'.$teacherId.' was not found.', 'debug');
5428        }
5429
5430        return json_encode(array('status' => false));
5431    }
5432
5433    /**
5434     * @api {post} /teacher/api/saveMemo saveMemo()
5435     * @apiName saveMemo
5436     * @apiGroup API
5437     * @apiDescription used to save the memo.
5438     * @apiSampleRequest off
5439     * 
5440     * @apiBody {String} id The ID of the user.
5441     * @apiBody {String} form_type The type of the form (form1 or form2).
5442     * @apiBody {String} date The date of the memo.
5443     * @apiBody {String} memo The memo content.
5444     * @apiBody {String} level The level for form2.
5445     * @apiBody {String} checklist The checklist for form2.
5446     * @apiBody {String} lessonId The lesson ID for form2.
5447     * @apiBody {String} recommendedTextbooks The recommended textbooks for form2.
5448     * @apiBody {String} lessonType The lesson type for form2.
5449     * @apiBody {String} lessonTrackId The lesson track ID.
5450     *
5451     * @apiSuccess {String} note The updated note.
5452     * @apiSuccess {Boolean} status The status of the request.
5453     * 
5454     * @apiErrorExample {json} Success-Response:
5455     *     {
5456     *       "note": "Updated note",
5457     *       "status": true
5458     *     }
5459     *
5460      * @apiErrorExample {js} Used in: AngularJS
5461      * Location: "webroot/js/ng/controller/student_info.js"
5462      * Location: "webroot/js/ng/app.js"
5463     */
5464    public function saveMemo() {
5465        $this->autoRender = false;
5466        if ($this->request->is('post')) {
5467            $data = $this->request->data;
5468            $id = $data['id'];
5469            $teacherId = $this->Auth->User('id');
5470            $teacherName = $this->Auth->User('jp_name');
5471            //return error message for invalid or null id
5472            if (!$id) {
5473                return json_encode(array('status' => false, 'content' => 'Invalid ID'));
5474            }
5475            $user = $this->User->find('first', array(
5476                'fields'=>array('note_counselor','note_textbook'),
5477                'conditions' => array('User.id' => $id),
5478                'recursive' => -1
5479            ));
5480
5481            //checks if date and nickname are empty or not
5482            $data['date'] = isset($data['date'])? $data['date'] : '';
5483            $userName['User']['nickname'] = isset($user['User']['nickname'])? $user['User']['nickname'] : '';
5484            $saveSelectedTextbook = true;
5485            //check if form type is equals to form 1 and also check if fields are empty or not
5486            if ($data['form_type'] == 'form1'){
5487                $data['memo'] = isset($data['memo'])? $data['memo'] : '';
5488                if($data['date'] == '' || $data['memo'] == ''){
5489                    return json_encode(array('status' => false, 'content' => 'Input Missing Fields'));
5490                }
5491            }
5492            //check if form type is equals to form 2 and also check if fields are empty or not
5493            if ($data['form_type'] == 'form2'){
5494                //validation for level check [maximum 7]
5495                if($data['level'] > Configure::read('maximum_level_option')) {
5496                    return json_encode(array('status' => false, 'content' => 'Level maximum of 7'));
5497                }
5498
5499                // NC-5729 get the Level
5500                if (isset($data['level']) && $data['level'] == '' || $data['level'] == '0') {
5501                    $data['level'] = 'No Level Check';
5502                } else if (isset($data['level']) && $data['level'] === 'only_lvl_chck') {
5503                    $data['level'] = 'Only Level Check';
5504                } else if (isset($data['level']) && $data['level'] > 0) {
5505                    $data['level'] = $data['level'];
5506                } else if (!isset($data['level'])) {
5507                    $data['level'] = 'No Level Check';
5508                }
5509
5510                $data['checklist'] = isset($data['checklist'])? $data['checklist'] : '';
5511                if ($data['date'] == '' || $data['level'] == '' || $data['checklist'] == ''){
5512                    return json_encode(array('status' => false, 'content' => 'Input Missing Fields'));
5513                }
5514                $data['memo'] = "Level: ". $data['level']."\n".$data['checklist'];
5515                
5516                // NC-6757 Counseling save selected textbooks to counselor message form
5517                if ($data['lessonId']) {
5518                    $saveSelectedTextbookArr = array(
5519                        'lessonId' => $data['lessonId'],
5520                        'recommendedTextbooks' => $data['recommendedTextbooks'],
5521                        'lessonType' => $data['lessonType']
5522                    );
5523                    $this->log("NJ-4464 => saveMemo : " . json_encode($saveSelectedTextbookArr), "lesson_history_recovery");
5524                    $saveSelectedTextbook = $this->saveSelectedTextbook($saveSelectedTextbookArr);
5525                }
5526            }
5527            $lessonTrackId = (isset($data['lessonTrackId']) && $data['lessonTrackId']) ? $data['lessonTrackId'] : "";
5528            if ($data['form_type'] == 'form1'){
5529                $memo = $data['date'] . "【" . $teacherId . " " .$teacherName . "】" .$lessonTrackId. "\n" . $data['memo'] ."\n\n".$user['User']['note_counselor'];
5530                $saveField = 'note_counselor';
5531            }else{
5532                $memo = $data['date'] . "【" . $teacherId . " " .$teacherName . "】" .$lessonTrackId. "\n" . $data['memo'] ."\n\n".$user['User']['note_textbook'];
5533                $saveField = 'note_textbook';
5534            }
5535            $this->User->read(null, $id);
5536            if ($saveSelectedTextbook) {
5537                if($this->User->saveField($saveField, $memo)) {
5538                    return json_encode($memo);
5539                }
5540            }
5541        }
5542        return json_encode(array('status' => false));
5543    }
5544
5545    
5546    // NC-6757 Counseling save selected textbooks to counselor message form
5547    //only used in this controller
5548    public function saveSelectedTextbook ($params) {
5549        $this->autoRender = false;
5550
5551        if ($params) {
5552            $lessonId = $params['lessonId'];
5553            $lessonType = $params['lessonType'];
5554            $recommendedTextbooks = $params['recommendedTextbooks'];
5555            if ((isset($lessonId) && $lessonId) && (isset($recommendedTextbooks) && $recommendedTextbooks)) {
5556
5557                $updateParams = array(
5558                    'lessonId' => $lessonId,
5559                    'recommendedTextbooks' => $recommendedTextbooks
5560                );
5561
5562                if ($lessonType == 'onair') {
5563                    return $this->saveSelectedTextbookLessonOnAir($updateParams);
5564                } else if ($lessonType == 'onairLog') {
5565                    return $this->saveSelectedTextbookLessonOnAirLogs($updateParams);
5566                }
5567                return false;
5568
5569            }
5570            return false;
5571
5572        }
5573        return false;
5574    }
5575
5576    //only used in this controller
5577    public function saveSelectedTextbookLessonOnAir ($params) {
5578        $lessonOnair = $this->LessonOnair->find('first', array(
5579                'conditions' => array(
5580                    'LessonOnair.id' => $params['lessonId']
5581                ),
5582                'fields' => array(
5583                    'LessonOnair.id',
5584                    'LessonOnair.chat_hash',
5585                    'LessonOnair.lesson_memo'
5586                ),
5587                'recursive' => -1
5588            )
5589        );
5590
5591
5592        if ($lessonOnair) {
5593            $lessonMemo = $lessonOnair["LessonOnair"]["lesson_memo"];
5594            if ($lessonMemo != null) {
5595                $lessonMemoArr = json_decode($lessonMemo, true);
5596                $lessonMemoArr['message_19'] = array($params['recommendedTextbooks']);
5597            } else {
5598                $lessonMemoArr = array();
5599                $lessonMemoArr['message_10'] = array();
5600                $lessonMemoArr['message_19'] = array($params['recommendedTextbooks']);;
5601                $lessonMemoArr['message_20'] = array();
5602            }
5603
5604            $updateMemo = array(
5605                "id" => $lessonOnair["LessonOnair"]["id"],
5606                "lesson_memo" => json_encode($lessonMemoArr),
5607            );
5608
5609            $this->LessonOnair->clear();
5610            $this->LessonOnair->set($updateMemo);
5611            if ($this->LessonOnair->save()) {
5612                return true;
5613            }
5614            return false;
5615
5616        }
5617        return false;
5618
5619
5620    }
5621
5622    //only used in this controller
5623    public function saveSelectedTextbookLessonOnAirLogs ($params) {
5624        $lessonOnairsLog = $this->LessonOnairsLog->find('first', array(
5625                'conditions' => array(
5626                    'LessonOnairsLog.id' => $params['lessonId']
5627                ),
5628                'fields' => array(
5629                    'LessonOnairsLog.id',
5630                    'LessonOnairsLog.chat_hash',
5631                    'LessonOnairsLog.lesson_memo'
5632                ),
5633                'recursive' => -1
5634            )
5635        );
5636
5637        if ($lessonOnairsLog) {
5638            $lessonMemo = $lessonOnairsLog["LessonOnairsLog"]["lesson_memo"];
5639            if ($lessonMemo != null) {
5640                $lessonMemoArr = json_decode($lessonMemo, true);
5641                $lessonMemoArr['message_19'] = array($params['recommendedTextbooks']);
5642            } else {
5643                $lessonMemoArr = array();
5644                $lessonMemoArr['message_10'] = array();
5645                $lessonMemoArr['message_19'] = array($params['recommendedTextbooks']);;
5646                $lessonMemoArr['message_20'] = array();
5647            }
5648
5649            $updateMemo = array(
5650                "id" => $lessonOnairsLog["LessonOnairsLog"]["id"],
5651                "lesson_memo" => json_encode($lessonMemoArr),
5652            );
5653            $this->LessonOnairsLog->clear();
5654            $this->LessonOnairsLog->set($updateMemo);
5655            if ($this->LessonOnairsLog->save()) {
5656                return true;
5657            }
5658            return false;
5659
5660        }
5661        return false;
5662
5663    }
5664
5665    /**
5666     * @api {post} /teacher/api/updateMemo updateMemo()
5667     * @apiName updateMemo
5668     * @apiGroup API
5669     * @apiDescription used to update the memo.
5670     * @apiSampleRequest off
5671     * 
5672     * @apiBody {String} userId The ID of the user.
5673     * @apiBody {String} note The note to be saved.
5674     * @apiBody {String} noteType The type of the note (memo or textbook).
5675     *
5676     * @apiSuccess {String} note The updated note.
5677     * @apiSuccess {Boolean} status The status of the request.
5678     * 
5679     * @apiErrorExample {json} Success-Response:
5680     *     {
5681     *       "note": "Updated note",
5682     *       "status": true
5683     *     }
5684     *
5685     * @apiErrorExample {json} Error-Response:
5686     *     {
5687     *       "status": false
5688     *     }
5689     * 
5690      * @apiErrorExample {js} Used in: AngularJS
5691      * Location: "webroot/js/ng/controller/student_info.js"
5692     */
5693    public function updateMemo(){
5694        $this->autoRender = false;
5695        if ($this->request->is('post')) {
5696            $data = $this->request->data;
5697            if(isset($data['userId'])) {
5698                $userId = $data['userId'];
5699                // NC-5729 : @modified allow empty string to clear the notes.
5700                if (isset($data['note']) && isset($data['noteType'])) {
5701                    $note = $data['note'];
5702                    $saveField =  $data['noteType'] == 'memo' ? 'note_counselor' : 'note_textbook';
5703                    $this->User->read([$saveField], $userId);
5704                    if($this->User->saveField($saveField, strip_tags($note))){
5705                        return json_encode(array('note' => $note,'status' => true));
5706                    }
5707                }
5708            }
5709        }
5710        return json_encode(array('status' => false));
5711    }
5712
5713    /**
5714     * @api {post} /teacher/api/lessonPeriod lessonPeriod()
5715     * @apiName lessonPeriod
5716     * @apiGroup API
5717     * @apiDescription Get the lesson period of the user.
5718     * @apiSampleRequest off
5719     * 
5720     * @apiBody {String} userId The ID of the user.
5721     * @apiBody {String} startDate The start date of the lesson period.
5722     * @apiBody {String} endDate The end date of the lesson period.
5723     *
5724     * @apiSuccess {String} subcategories The subcategories of the user.
5725     * @apiError {Boolean} error The status of the request.
5726     * @apiError {String} content The content of the request.
5727     * 
5728     * @apiErrorExample {json} Success-Response:
5729     *     {
5730     *       "subcategories": "Category Subcategory: 3回<br>..."
5731     *     }
5732     *
5733     * @apiErrorExample {json} Error-Response:
5734     *     {
5735     *       "error": true,
5736     *       "content": "Lesson Period Date is required"
5737     *     }
5738     * 
5739      * @apiErrorExample {js} Used in: AngularJS
5740      * Location: "webroot/js/ng/controller/student_info.js"
5741     */
5742    public function lessonPeriod() {
5743        $this->autoRender = false;
5744        if ($this->request->is('post')) {
5745            $data = $this->request->data;
5746            $userId = $data['userId'];
5747            //check if date is not empty
5748            $startDate = isset($data['startDate'])? $data['startDate'] : '';
5749            $endDate =  isset($data['endDate'])? $data['endDate'] : '';
5750
5751            if($startDate == '' || $endDate == ''){
5752                return json_encode(array('error' => true, 'content' => 'Lesson Period Date is required'));
5753            }
5754
5755            $lessons = $this->LessonOnairsLog->find('all', array(
5756                    'fields' => array(
5757                        'TextbookSubCategory.name',
5758                        'TextbookCategory.name',
5759                        'Count(TextbookSubCategory.name) as times'
5760                    ),
5761                    'conditions'=> array(
5762                        'LessonOnairsLog.user_id' => $userId,
5763                        'LessonOnairsLog.start_time >= ? AND LessonOnairsLog.start_time <= ?' => array($startDate, $endDate),
5764                        'LessonOnairsLog.connect_id !=' => NULL
5765                    ),
5766                    'joins' => array (
5767                        array (
5768                            'type' => 'LEFT',
5769                            'table' => 'users',
5770                            'alias' => 'Users',
5771                            'conditions' => 'Users.id = LessonOnairsLog.user_id'
5772                        ),
5773                        array (
5774                            'type' => 'LEFT',
5775                            'table' => 'textbook_connects',
5776                            'alias' => 'TextbookConnect',
5777                            'conditions' => 'LessonOnairsLog.connect_id = TextbookConnect.id'
5778                        ),
5779                        array (
5780                            'type' => 'LEFT',
5781                            'table' => 'textbook_categories',
5782                            'alias' => 'TextbookCategory',
5783                            'conditions' => 'TextbookConnect.category_id = TextbookCategory.id'
5784                        ),
5785                        array(
5786                            'type' => 'LEFT',
5787                            'table' => 'textbook_subcategories',
5788                            'alias' => 'TextbookSubCategory',
5789                            'conditions' => 'TextbookSubCategory.id = TextbookConnect.subcategory_id'
5790                        ),
5791                    ),
5792                    'order' =>array('LessonOnairsLog.start_time' => 'DESC'),
5793                    'group' => array('TextbookSubCategory.id')
5794                )
5795            );
5796            //declare variable subcategories
5797            $subcategories = '';
5798            $textbooks = '';
5799            foreach ($lessons as $key => $lesson){
5800                $categoryName = $lesson['TextbookCategory']['name'];
5801                $textBook = $lesson['TextbookSubCategory']['name'];
5802                $count = $lesson[0]['times'];
5803                //return subcategories based on the lesson date period
5804                $subcategories .= $categoryName .' '. $textBook . ": " . $count . "回" ."<br>" ;
5805             }
5806            return $subcategories;
5807        }
5808    }
5809
5810    /**
5811     * @api {post} /teacher/api/checkTimezone checkTimezone()
5812     * @apiName checkTimezone
5813     * @apiGroup API
5814     * @apiSampleRequest off
5815    * @apiDescription used to check if the local timezone and timezone setting save is the same.
5816     *
5817     * @apiBody {String} timezoneName The name of the timezone to check.
5818     *
5819    * @apiSuccess {Boolean} status The status of the request.
5820    * @apiError {Boolean} status The status of the request.
5821    * @apiError {String} error The error message.
5822    * 
5823     * @apiErrorExample {json} Success-Response:
5824     *     {
5825     *       "status": true
5826     *     }
5827     *
5828     * @apiErrorExample {json} Error-Response:
5829     *     {
5830     *       "status": false,
5831     *       "error": "timezone_not_supported"
5832     *     }
5833    *
5834     * @apiErrorExample {js} Used in: AngularJS
5835     * Location: "webroot/js/ng/app.js"
5836     * Location: "webroot/js/ng/controller/dashboard.js"
5837     */
5838    /**
5839     * NC-3715
5840     * check if local timezone and timezone setting save is the same.
5841     * @param string $timezoneName
5842     * @return boolean json_encode array status true | false
5843     */
5844    public function checkTimezone() {
5845        $this->autoRender = false;
5846        $teacherId = $this->Auth->user('id');
5847
5848        $memcached = new myMemcached();
5849        $timezoneToken = $memcached->get('timezoneChangeNoticeFlg'.$teacherId);
5850
5851        // check if token for checking timezone at login is not exist
5852        if (!$timezoneToken) {
5853            return json_encode(array('status' => true));
5854        }
5855
5856        $memcached->delete('timezoneChangeNoticeFlg'.$teacherId);
5857
5858        // if not post request
5859        if (!$this->request->is('post')) {
5860            $this->log("TIMEZONE: request is not post", "debug");
5861            throw new Exception("invalid_request_type");
5862        }
5863
5864        if (!isset($this->request->data['timezoneName'])) {
5865            $this->log("TIMEZONE: param timezoneName missing.", "debug");
5866            return json_encode(array('status' => false, 'error' => 'timezone_not_supported'));
5867        }
5868
5869        // get current timezone name
5870        $currentTimezoneName = date_default_timezone_get();
5871
5872        $timezoneName = $this->request->data['timezoneName'];
5873        $tznArr = explode('/', $timezoneName);
5874        $continents = array_flip(Configure::read('continents'));
5875
5876        if (!isset($tznArr[0]) || (isset($tznArr[0]) && empty($continents[$tznArr[0]]))) {
5877            $this->log('Timezone not supported. teacher_id: ' . $teacherId. ' post data --> ' . json_encode($this->request->data), 'timezone_debug');
5878            return json_encode(array('status' => false, 'error' => 'timezone_not_supported'));
5879        }
5880        //set city
5881        $city = isset($tznArr[1]) ? $tznArr[1] : '';
5882        $city .= isset($tznArr[2]) ? '/' . $tznArr[2] : '';
5883
5884        date_default_timezone_set($timezoneName);
5885        $userTz = new DateTimeZone($timezoneName);
5886        $userTime = new DateTime('now', $userTz);
5887
5888        $offset = date_offset_get(new DateTime);
5889        $daylightSavingTime = date('I');
5890
5891        $localTime = strtotime($userTime->format('Y-m-d H:i:s'));
5892        $localDateDisplay = $userTime->format('d/m/Y(D) g:i A');
5893
5894        $serverTz = new DateTimeZone($currentTimezoneName);
5895        $serverTime = new DateTime('now', $serverTz);
5896        $serverTime =  strtotime($serverTime->format('Y-m-d H:i:s'));
5897        date_default_timezone_set($currentTimezoneName);
5898
5899        //adjust to timezone
5900        if ($daylightSavingTime) {
5901            $offset -= 3600;
5902            $localTime -= 3600;
5903        }
5904
5905        $timezoneModel = $this->Timezone;
5906
5907        // get timezone id
5908        $params = array(
5909            'type' => 'first',
5910            'args' => array(
5911                'fields' => array(
5912                    'id',
5913                    'utc_offset',
5914                    'city_eng',
5915                    'country_id'
5916                ),
5917                'conditions' => array(
5918                    'continent_id' => $continents[$tznArr[0]],
5919                    'city_eng' => $city
5920                )
5921            )
5922        );
5923
5924        $timezone = $timezoneModel->getTimezones($params);
5925        $sameTimezone = false;
5926        $timezoneSetDisplay = '';
5927        
5928        $mem = new myMemcached();
5929        $memTimezone = $mem->get('timezone'.$teacherId);
5930
5931        if (empty($timezone)) {
5932            $timeDiff = ($localTime - $serverTime)/60;
5933            $utcOffset = myTools::offsetFormat($offset/60);
5934            $timezonesAndCountryCodes = $this->Timezone->getTimezonesAndCountryCodes();
5935            $countryCode = isset($timezonesAndCountryCodes[$timezoneName]) ? $timezonesAndCountryCodes[$timezoneName] : null;
5936
5937            // log if timezone not exist on timezones and country codes list.
5938            if (!$countryCode) {
5939                $this->log('Timezone does not exist on timezonesAndCountryCodes list. teacher_id: ' . $teacherId . ' --> ' . json_encode($timezoneName) . ' TZ_params: ' . json_encode($this->request->data), 'timezone_debug');
5940            }
5941
5942            $saveData = array(
5943                'continent_id' => $continents[$tznArr[0]],
5944                'city_eng' => $city, // city
5945                'city_jp' => $city, // city
5946                'country_code_id' => $countryCode,
5947                'utc_offset' => $utcOffset,
5948                'jp_time_diff' => $timeDiff
5949            );
5950
5951            $timezone = $timezoneModel->saveTimeZone($saveData);
5952
5953            // if successfully save new timezone
5954            if ($timezone) {
5955                // set reset timezones memcached
5956                $memcached->set(array(
5957                    'key' => 'reset_timezones_memcached',
5958                    'value' => true,
5959                ));
5960                
5961                // - NJ-3653 if have new timezone delete select_all_country_codes memcache
5962                $memKey = 'select_all_country_codes';
5963                $mem->delete($memKey);
5964                // - after deleting timezon memcache add new one
5965                $this->Timezone->countryOptions();
5966            }
5967        } else {
5968            // check if local timezone is same to the current timezone setting.
5969            if (
5970                !empty($memTimezone) && $memTimezone['continent_id'] == $continents[$tznArr[0]] &&
5971                $memTimezone['city_eng'] == $tznArr[1] && $this->Auth->user('timezone_dst_flg') == $daylightSavingTime
5972            ) {
5973                $sameTimezone = true;
5974            } else {
5975                $timeDiff = myTools::timestampWithTimeDiff($localTime, $this->timeDiff);
5976                $timezoneSetDisplay = date('d/m/Y(D) g:i A', $timeDiff) . ' ('.$memTimezone['city_eng'].', '.$memTimezone['country_name'].' UTC'.$memTimezone['utc_offset'].')';
5977            }
5978        }
5979
5980        // update teacher timezone data if timezone_id is null
5981        $timezoneId = $this->Auth->user('timezone_id');
5982        if (empty($timezoneId)) {
5983            $this->Teacher->updateTeacher(
5984                array('timezone_id' => $timezone['Timezone']['id'], 'timezone_dst_flg' => $daylightSavingTime),
5985                $teacherId,
5986                true
5987            );
5988
5989            $params = array(
5990                'id' => $timezone['Timezone']['id'],
5991                'teacherId' => $teacherId
5992            );
5993            $this->Timezone->memTeacherTimezone($params);
5994            $sameTimezone = true;
5995        } else {
5996            $timeDiff = myTools::timestampWithTimeDiff($localTime, $this->timeDiff);
5997            $timezoneSetDisplay = date('d/m/Y(D) g:i A', $timeDiff).' ('.$memTimezone['city_eng'].', '.$memTimezone['country_name'].' UTC'.$memTimezone['utc_offset'].')';
5998        }
5999
6000        if ($sameTimezone) {
6001            return json_encode(array('status' => true));
6002        } else {
6003            $this->log('teacher_id: ' . $teacherId . 
6004                'TZ_params: ' . json_encode($this->request->data) . 
6005                ' user_timezone: ' . json_encode($timezone) . 
6006                ' daylightSavingTime: ' . json_encode($daylightSavingTime) . 
6007                ' localDateDisplay: ' . json_encode($localDateDisplay) .
6008                ' timezoneSetDisplay: ' . json_encode($timezoneSetDisplay)
6009            , 'timezone_debug');
6010            return json_encode(array(
6011                'status' => false,
6012                'data' => array(
6013                    'timezone_id' => $timezone['Timezone']['id'],
6014                    'timezone_dst_flg' => $daylightSavingTime,
6015                    'localDateDisplay' => $localDateDisplay,
6016                    'timezoneSetDisplay' => $timezoneSetDisplay
6017                )
6018            ));
6019        }
6020    }
6021
6022    /**
6023     * @api {post} /teacher/api/updateTeacherTimezone updateTeacherTimezone()
6024     * @apiName updateTeacherTimezone
6025     * @apiGroup API
6026     * @apiDescription Update the teacher timezone.
6027     * @apiSampleRequest off
6028     * 
6029     * @apiBody {Number} timezone_id The ID of the new timezone.
6030     * @apiBody {Boolean} timezone_dst_flg The DST flag for the new timezone.
6031     *
6032     * @apiSuccess {Boolean} success The status of the request.
6033     * @apiError {Boolean} success The status of the request.
6034     * @apiError {Boolean} excessSlotsFlg The status of the request.
6035     * 
6036     * @apiErrorExample {json} Success-Response:
6037     *     {
6038     *       "success": true
6039     *     }
6040     *
6041     * @apiErrorExample {json} Error-Response:
6042     *     {
6043     *       "success": false,
6044     *       "excessSlotsFlg": true
6045     *     }
6046     * 
6047      * @apiErrorExample {js} Used in: AngularJS
6048      * Location: "webroot/js/ng/app.js"
6049     */
6050    /**
6051     * NC-3715
6052     * update teacher timezone
6053     * post request
6054     * @return boolean json_encode array success true | false
6055     */
6056    public function updateTeacherTimezone() {
6057        $this->autoRender = false;
6058        $res = false;
6059
6060        // if not post request
6061        if (!$this->request->is('post')) {
6062            $this->log("TIMEZONE: request is not post", "debug");
6063            throw new Exception("invalid_request_type");
6064        }
6065
6066        $data = $this->request->data;
6067
6068        // if missing parameter(s)
6069        if (!isset($data['timezone_id']) || !isset($data['timezone_dst_flg'])) {
6070            $this->log("TIMEZONE: missing parameter(s)", "debug");
6071            throw new Exception("TIMEZONE: missing parameter(s).");
6072        }
6073        
6074        // if new timezone > 24 slots
6075        $precheck = $this->ShiftWorkOn->preCheckTeacherSlotCount($data['timezone_id'], $this->Auth->user('id'));
6076        if ($precheck) {
6077            $this->log("TIMEZONE: Update to update to new timezone.", "debug");
6078            return json_encode(array('success' => false, 'excessSlotsFlg' => true));
6079        }
6080
6081        $removeValidation = true;
6082        $teacherId = $this->Auth->user('id');
6083
6084        if ($this->Teacher->updateTeacher($data, $teacherId, $removeValidation)) {
6085            $res = true;
6086
6087            $memcached = new myMemcached();
6088
6089            // delete current memcached for japan timezone difference
6090            $memcached->delete('timezoneChangeNoticeFlg'.$teacherId);
6091
6092            $params = array(
6093                'id' => $data['timezone_id'],
6094                'dst' => $data['timezone_dst_flg'],
6095                'teacherId' => $teacherId
6096            );
6097
6098            // set memcached time difference
6099            $this->Timezone->memTeacherTimezone($params);
6100        }
6101
6102        return json_encode(array('success' => $res));
6103    }
6104
6105    /**
6106     * @api {post} /teacher/api/getServerAndLocalTime getServerAndLocalTime()
6107     * @apiName getServerAndLocalTime
6108     * @apiGroup API
6109     * @apiDescription Get server and local time.
6110     * @apiSampleRequest off
6111     * 
6112     * @apiSuccess {Number} serverTimeParsed The server time parsed.
6113     * @apiSuccess {String} serverTime The server time.
6114     * @apiSuccess {String} serverTimeFormatted The server time formatted.
6115     * @apiSuccess {String} localTime The local time.
6116     * @apiSuccess {String} localFlag The local flag.
6117     * @apiSuccess {String} localTimeFormatted The local time formatted.
6118     * @apiSuccess {String} localTime12H The local time 12H.
6119     * @apiSuccess {String} serverTime12H2 The server time 12H.
6120     * 
6121     * @apiErrorExample {json} Success-Response:
6122     *     {
6123     *       "serverTimeParsed": 1633024800,
6124     *       "serverTime": "Oct 01,2021",
6125     *       "serverTimeFormatted": "10:00 AM",
6126     *       "localTime": "Oct 01,2021",
6127     *       "localFlag": "/user/images/flag/other.png",
6128     *       "localTimeFormatted": "10:00 AM",
6129     *       "localTime12H": "10:00:AM",
6130     *       "serverTime12H2": "10:00:AM"
6131     *     }
6132     *
6133      * @apiErrorExample {js} Used in: AngularJS
6134      * Location: "webroot/js/ng/controller/account.js"
6135      * Location: "webroot/js/ng/controller/home.js"
6136      * Location: "webroot/js/ng/controller/recruit.js"
6137      * Location: "webroot/js/ng/app.js"
6138      * Location: "webroot/js/ng/home.js"
6139     */
6140    /**
6141     * NC-3715
6142     * get serG2150er and local time
6143     * @return mixed json encoded array
6144     */
6145    public function getServerAndLocalTime() {
6146        $this->autoRender = false;
6147
6148        // if not post request
6149        if (!$this->request->is('post')) {
6150            $this->log("TIMEZONE: request is not post", "debug");
6151            throw new Exception("invalid_request_type");
6152        }
6153
6154        /* if office teacher 
6155            NC-9998 : comment this block of code
6156        */
6157        // if (!$this->Auth->user('home_flg')) {
6158        //     return json_encode(array('status' => false));
6159        // }
6160
6161        $teacherID = $this->Auth->user('id');
6162        //NJ-11421 : get teacher time zone data 
6163        $localFlag = "other.png";
6164
6165        //find the country code data 
6166
6167        $this->Teacher->openDBReplica();
6168
6169        $countryData = $this->Teacher->find('first',array(
6170            'fields' => array(
6171                    'Teacher.id',
6172                    'CountryCode.country_name'
6173            ),
6174            'joins' => array(
6175                array(
6176                    'table' => 'timezones',
6177                    'alias' => 'Timezone',
6178                    'type' => 'LEFT',
6179                    'conditions' => array('Timezone.id = Teacher.timezone_id')
6180                ),
6181                array(
6182                    'table' => 'country_codes',
6183                    'alias' => 'CountryCode',
6184                    'type' => 'LEFT',
6185                    'conditions' => array('Timezone.country_code_id = CountryCode.code')
6186                )
6187            ),
6188            'conditions' => array('Teacher.id' => $teacherID),
6189            'recursive' => -1
6190        ));
6191
6192        $this->Teacher->closeDBReplica();
6193
6194
6195        $localFlag = (isset($countryData['CountryCode']['country_name'])) ? strtolower(str_replace(' ', '_', $countryData['CountryCode']['country_name'])) . '.png' : $localFlag;
6196
6197
6198        if (!file_exists(ROOT . "/user/webroot/images/flag/" . $localFlag)) {
6199            $localFlag = 'other.png';
6200        }
6201
6202        $localFlag = '/user/images/flag/'.$localFlag;
6203
6204
6205        $datetime = date('Y-m-d H:i:s');
6206        $serverTime = date('M d,Y');
6207        $rawLocalTime = myTools::timestampWithTimeDiff($datetime, $this->timeDiff);
6208        $localTime = myTools::datetimeFormatted($rawLocalTime);
6209        $localTime12H = date('g:i A',$rawLocalTime);
6210        $serverTime12H = date('g:i A');
6211        $serverTime12H2 = date('g:i:A');
6212        $seconds = date('s');
6213
6214        $returnData =  array(
6215            'serverTimeParsed' => strtotime($datetime),
6216            'serverTime' => $serverTime,
6217            'serverTimeFormatted' => $serverTime12H, 
6218            'localTime' => date("M d,Y",$rawLocalTime),
6219            'localFlag' => $localFlag,
6220            'localTimeFormatted' => $localTime12H,
6221            'localTime12H' => date('g:i:A',$rawLocalTime),
6222            'serverTime12H2' => $serverTime12H2,
6223        );
6224
6225        return json_encode($returnData);
6226    }
6227
6228    /**
6229     * @api {post} /teacher/api/checkDaylightSavingTime checkDaylightSavingTime()
6230     * @apiName checkDaylightSavingTime
6231     * @apiGroup API
6232     * @apiDescription Return 1 if post data timezone name does have daylight saving time, else 0
6233     * @apiSampleRequest off
6234     * 
6235     * @apiBody {String} timezoneName The name of the timezone to check for DST.
6236     *
6237     * @apiSuccess {Number} dst The DST flag.
6238     * 
6239     * @apiErrorExample {json} Success-Response:
6240     *     {
6241     *       "dst": 1
6242     *     }
6243     *
6244     * @apiErrorExample {json} Error-Response:
6245     *     {
6246     *       "dst": 0
6247     *     }
6248     * 
6249      * @apiErrorExample {js} Used in: AngularJS
6250      * Location: "webroot/js/ng/controller/account.js"
6251      * Location: "webroot/recruitment/js/common.js"
6252     */
6253    /**
6254     * Return 1 if post data timezone name does have daylight saving time, else 0
6255     */
6256    public function checkDaylightSavingTime() {
6257        $this->autoRender = false;
6258        $dst = 0;
6259        if ($this->request->is('post')) {
6260            $postData = $this->request->data;
6261            $dst = myTools::getDaylightSavingTime($postData['timezoneName']);
6262        }
6263
6264        return json_encode(array('dst' => $dst));
6265    }
6266
6267    //only used in this controller
6268    public function getOpenedSlot() {
6269        $this->autoRender = false;
6270        return $this->ShiftWorkOn->checkOpenedSlot($this->Auth->user('id'));
6271    }
6272
6273    /**
6274     * @api {post} /teacher/api/deleteEqualTo5MinutesAndBelowModal deleteEqualTo5MinutesAndBelowModal()
6275     * @apiName deleteEqualTo5MinutesAndBelowModal
6276     * @apiGroup API
6277     * @apiDescription Delete memcache key EqualTo5MinutesAndBelowModal_teacher_id
6278     * @apiSampleRequest off
6279     * 
6280     * @apiSuccess {Boolean} success The status of the request.
6281     * 
6282     * @apiErrorExample {json} Success-Response:
6283     *     {
6284     *       "success": true
6285     *     }
6286     *
6287     * @apiErrorExample {json} Error-Response:
6288     *     {
6289     *       "success": false
6290     *     }
6291     * 
6292     * @apiErrorExample {js} Used in: Elements
6293     * Location: "view/Elements/chat_area_js.php"
6294     */
6295    /**
6296     * NC-4027
6297     * memcache delete EqualTo5MinutesAndBelowModal_[teacher_id]
6298     * return boolean true | false
6299     */
6300    public function deleteEqualTo5MinutesAndBelowModal() {
6301        $this->autoRender = false;
6302        $teacherId = $this->Auth->user('id');
6303
6304        // if not ajax request
6305        if (!$this->request->is('ajax')) {
6306            $this->log("[3_MINUTES_BREAKTIME] request is not AJAX", "debug");
6307            throw new Exception("invalid_request_type");
6308        }
6309
6310        // add user to memcached array
6311        $memcached = new myMemcached();
6312
6313        // delete memcached
6314        $memcached->delete('EqualTo5MinutesAndBelowModal_' . $teacherId);
6315
6316        // check if successfully deleted
6317        if (!$memcached->get('EqualTo5MinutesAndBelowModal_' . $teacherId)) {
6318            $success = true;
6319        } else {
6320            $success = false;
6321        }
6322
6323        return json_encode(array('success' => $success));
6324    }
6325
6326    /**
6327     * @api {post} /teacher/api/updateCounselingAttendedFlg updateCounselingAttendedFlg()
6328     * @apiName updateCounselingAttendedFlg
6329     * @apiGroup API
6330     * @apiDescription Update the counseling attended flag.
6331     * @apiSampleRequest off
6332     * 
6333     * @apiBody {String} userId The ID of the user.
6334     * @apiBody {String} counselingAttendedFlg The flag to be saved.
6335     * 
6336     * @apiSuccess {Boolean} success The status of the request.
6337     * 
6338     * @apiErrorExample {json} Success-Response:
6339     *     {
6340     *       "success": true
6341     *     }
6342     *
6343     * @apiErrorExample {json} Error-Response:
6344     *     {
6345     *       "success": false
6346     *     }
6347     * 
6348      * @apiErrorExample {js} Used in: AngularJS
6349      * Location: "webroot/js/ng/controller/student_info.js"
6350     */
6351    public function updateCounselingAttendedFlg(){
6352        $this->autoRender = false;
6353        $result['success'] = false;
6354        if ($this->request->is('post')) {
6355            $data = $this->request->data;
6356            $this->User->read(array('counseling_attended_flg'), $data['userId']);
6357            $this->User->validate = false;
6358            $this->User->set(array('counseling_attended_flg' => $data['counselingAttendedFlg']));
6359            $this->User->save();
6360            $result['success'] = true;
6361        }
6362        return json_encode($result);
6363    }
6364
6365    /**
6366     * @api {get} /teacher/api/checkReservation checkReservation()
6367     * @apiName checkReservation
6368     * @apiGroup API
6369     * @apiDescription Check if teacher has reservation.
6370     * @apiSampleRequest off
6371     * 
6372     * @apiSuccess {Boolean} status The status of the request.
6373     * 
6374     * @apiErrorExample {json} Success-Response:
6375     *     {
6376     *       "status": "true"
6377     *     }
6378     *
6379     * @apiErrorExample {json} Error-Response:
6380     *     {
6381     *       "status": "false"
6382     *     }
6383     * 
6384      * @apiErrorExample {js} Used in: AngularJS
6385      * Location: "webroot/js/ng/controller/header.js"
6386      * Location: "webroot/js/ng/app.js"
6387      * Location: "webroot/js/recruitment/ng/app.js"
6388     */
6389    //NC-4522 add alert if teacher has reservation
6390    public function checkReservation() {
6391        $this->autoRender = false;
6392        if ($this->request->is('get')) {
6393            $teacherId = $this->Auth->user('id');
6394            $curTime = date("Y-m-d H:i:00", strtotime('-25 minute'));
6395            $countReservation = $this->LessonSchedule->find('count', array(
6396                'conditions' => array(
6397                    array('LessonSchedule.teacher_id' => $teacherId),
6398                    array('LessonSchedule.status' => Configure::read("lesson_schedule.status.on_reserve")),
6399                    array('LessonSchedule.lesson_time >=' => $curTime)
6400                )
6401            ));
6402            $hasReservation = "false";
6403            $memcached = new myMemcached();
6404            if ($memcached->get('teacherHasReservation_' . $teacherId) && $countReservation > 0) {
6405                $hasReservation = "true";
6406            }
6407            $memcached->delete('teacherHasReservation_' . $teacherId);
6408            return json_encode(array("status" => $hasReservation));
6409        }
6410        return json_encode(array("status" => "false"));
6411    }
6412
6413    // strip double curly braces for angularjs and sanitize inputs
6414    private function sanitizeInputs( $params = array() ){
6415        $result = array();
6416        if ( isset($params["data"]) && count($params["data"]) > 0 ) {
6417            foreach ($params["data"] as $key => $value) {
6418                $doubleCurlyBracesFilter = myTools::doubleCurlyBraceFilter($value);
6419                $result[$key] = $doubleCurlyBracesFilter;
6420            }
6421        } else {
6422            $result = $params["data"];
6423        }
6424        return $result;
6425    }
6426
6427    /**
6428     * @api {post} /teacher/api/getPopupList getPopupList()
6429     * @apiName getPopupList
6430     * @apiGroup API
6431     * @apiDescription Get the list of popups.
6432     * @apiSampleRequest off
6433     * 
6434     * @apiBody {String} material_category_id The ID of the material category.
6435     *
6436     * @apiSuccess {Array} popupList The list of popups.
6437     * 
6438     * @apiErrorExample {json} Success-Response:
6439     *     {
6440     *       "popupList": [...]
6441     *     }
6442     * 
6443     * @apiErrorExample {js} Used in: Elements
6444     * Location: "view/Elements/chat_area_js.php"
6445     */
6446    /**
6447    * NC-5253
6448    */
6449    public function getPopupList () {
6450        $this->autoRender = false;
6451        if ($this->request->is('post', 'ajax')) {
6452            $data = $this->request->data;
6453            $return = array();
6454            if (isset($data['material_category_id'])) {
6455                $params['conditions'] = array(
6456                    'LessonPopup.material_category_id' => $data['material_category_id']
6457                );
6458                $popUp = $this->LessonPopup->getLessonPopups($params);
6459                $return = $this->LessonPopup->segregateLessonPopups($popUp);
6460            }
6461            return json_encode($return);
6462        }
6463    }
6464
6465    /**
6466     * @api {post} /teacher/api/getTextbookCategoryId getTextbookCategoryId()
6467     * @apiName getTextbookCategoryId
6468     * @apiGroup API
6469     * @apiDescription Get the category ID using the connect ID.
6470     * @apiSampleRequest off
6471     * 
6472     * @apiBody {String} connectId The ID of the textbook connection.
6473     *
6474     * @apiSuccess {Number} category_id The category ID.
6475     * 
6476     * @apiErrorExample {json} Success-Response:
6477     *     {
6478     *       "category_id": 1
6479     *     }
6480     *
6481      * @apiErrorExample {js} Used in: AngularJS
6482      * Location: "webroot/js/webrtcv2/event.common.js"
6483     */
6484    /**
6485    * NC-5253 get category_id using connectId
6486    * @param int connectId,
6487    * @return int category_id
6488    */
6489    public function getTextbookCategoryId () {
6490        $this->autoRender = false;
6491        if ($this->request->is('post', 'ajax')) {
6492            $rdata = $this->request->data;
6493            $return = array();
6494
6495            if (isset($rdata['connectId'])) {
6496                $data = $this->TextbookConnect->useReplica()->find('first', array(
6497                    'fields' => array('TextbookConnect.category_id'),
6498                    'conditions' => array('TextbookConnect.id' => $rdata['connectId'])
6499                ));
6500                $return = isset($data['TextbookConnect']['category_id']) ? $data['TextbookConnect'] : $return ;
6501            }
6502
6503            return json_encode($return);
6504        }
6505    }
6506
6507    /**
6508    * Post prohibited word to slack
6509    */
6510    private function postSlack($slackData) {
6511        $mySlack = new mySlack();
6512        $mySlack->allow_test_channel = true;
6513        $mySlack->channel = myTools::checkChannel("#nc-prohibited-word", "#fdc-test-channel");
6514        $teacherTypeLabel = "office";
6515        $mySlack->link_names = true;
6516
6517        //check teacher type for tag recipients
6518        if ($slackData['homeFlag']) {
6519            //@kijima @nc-kawanami @nc-inanobe (for home-based teachers)
6520            $mySlack->text = "<!subteam^SQ1FJV0P2|group-home-prohibited-word>";
6521            $teacherTypeLabel = "home";
6522        } else {
6523            //@funahashi @nc-ueda  @kawahara @nc-takauji @nc-sawada (for office-based  teachers)
6524            $mySlack->text = "<!subteam^SQ11WAUUU|group-office-prohibited-word>";
6525        }
6526
6527        # Get the base url 
6528        $baseUrl = myTools::getBaseUrl();
6529
6530        # Initialize variable for creating slact text with link
6531        $teacherLink = "<$baseUrl/admin/instructor-manage/instructor/{$slackData['teacherId']}|{$slackData['teacherId']}>";
6532        $studentLink = "<$baseUrl/admin/user-manage/member/{$slackData['userId']}|{$slackData['userId']}>";
6533        $lessonLink = "<$baseUrl/admin/lesson-history?lesson_track_id={$slackData['lesson_number']}|{$slackData['lesson_number']}>";
6534        $messageLink = "";
6535
6536        # Customize Link for Message Management
6537        if(!empty($slackData['lesson_id'])) {
6538            if($slackData['is_lesson_onair']) {
6539                $lessonOnAir = $this->LessonOnair->find('first', [
6540                    'conditions' => ['LessonOnAir.id' => $slackData['lesson_id']],
6541                    'fields' => ['LessonOnAir.chat_hash']
6542                ]);
6543                if(!empty($lessonOnAir)) {
6544                    $messageLink = "<$baseUrl/admin/message-manage?teacher_id={$slackData['teacherId']}&user_id={$slackData['userId']}&chat_hash={$lessonOnAir['LessonOnair']['chat_hash']}|{$lessonOnAir['LessonOnair']['chat_hash']}>";
6545                }
6546            } else {
6547                $lessonOnAir = $this->LessonOnairsLog->find('first', [
6548                    'conditions' => ['LessonOnairsLog.id' => (int)$slackData['lesson_id']],
6549                    'fields' => ['LessonOnairsLog.chat_hash']
6550                ]);
6551                if(!empty($lessonOnAir)) {
6552                    $messageLink = "<$baseUrl/admin/message-manage?teacher_id={$slackData['teacherId']}&user_id={$slackData['userId']}&chat_hash={$lessonOnAir['LessonOnairsLog']['chat_hash']}|{$lessonOnAir['LessonOnairsLog']['chat_hash']}>";
6553                }
6554            }
6555        }
6556
6557        $mySlack->text .= "```";
6558        $mySlack->text .= "【Prohibited word was sent!】\n";
6559        $mySlack->text .= "Date: " . date('Y-m-d') . "\n";
6560        $mySlack->text .= "Teacher name : " . $slackData['teacherName'] . "\n";
6561        $mySlack->text .= "Teacher ID : " . $teacherLink . "\n";
6562        $mySlack->text .= "Teacher Type : " . $teacherTypeLabel . "\n";
6563        $mySlack->text .= "Student name : " . $slackData['userName'] . "\n";
6564        $mySlack->text .= "Student ID : " . $studentLink . "\n";
6565        $mySlack->text .= "Lesson ID : " . $lessonLink . "\n";
6566        $mySlack->text .= "Sent message : " . $messageLink . "\n";
6567        $mySlack->text .= "\n" . $slackData['comment'] . "\n";
6568        $mySlack->text .= "```";
6569        $mySlack->sendSlack();
6570    }
6571
6572    /**
6573    * NC-8372 : chivox error
6574    */
6575    private function sendChivoxSlack ($params = array()) {
6576        //initialize slack and send
6577        $mySlack = new mySlack();
6578        $content = "";
6579        $chivoxError = isset($params['error']) ? $params['error'] : "";
6580
6581        // Total
6582        $content .= "Error Content :" . "\n";
6583        $content .= $chivoxError . "\n";
6584        $content .= "Logs : " . "\n";
6585        $content .= json_encode($params);
6586
6587        $params = array("content" => $content);
6588        $mySlack->chivoxReport($params);
6589    }
6590
6591    /**
6592     * @api {post} /teacher/api/saveAdminStaffEvaluation saveAdminStaffEvaluation()
6593     * @apiName saveAdminStaffEvaluation
6594     * @apiGroup API
6595     * @apiDescription Save the admin staff evaluation.
6596     * @apiSampleRequest off
6597     * 
6598     * @apiBody {Number} rate The rate of the evaluation.
6599     * @apiBody {String} comment The comment of the evaluation.
6600     * @apiBody {Number} admin_id The ID of the admin.
6601     * @apiBody {Number} inquiry_id The ID of the inquiry.
6602     * 
6603     * @apiSuccess {Boolean} success The status of the request.
6604     * @apiSuccess {Boolean} exist The status of the evaluation.
6605     * @apiSuccess {Number} rate The rate of the evaluation.
6606     * 
6607     * @apiErrorExample {json} Success-Response:
6608     *     {
6609     *       "success": true,
6610     *       "exist": true,
6611     *       "rate": 5
6612     *     }
6613     *
6614      * @apiErrorExample {js} Used in: AngularJS
6615      * Location: "webroot/js/ng/app.js"
6616     */
6617    public function saveAdminStaffEvaluation() {
6618        $this->autoRender = false;
6619        $response['success'] = false;
6620        $teacherId =  $this->Auth->user('id');
6621        $home_flg = $this->Auth->user('home_flg');
6622
6623        if ($this->request->is('post')) {
6624            $data = $this->request->data;
6625
6626            $saveData = array(
6627                'member_id' => $teacherId,
6628                'evaluation_category' => 4,
6629                'rate' => $data['rate'],
6630                'comment' => $data['comment'],
6631                'member_type' => ($home_flg) ? 2 : 4,
6632                'admin_id' => $data['admin_id'],
6633                'inquiry_id' => $data['inquiry_id']
6634            );
6635            $evaluationChecker = myTools::evaluationChecker(array('category' => 4, 'inquiry_id' => $data['inquiry_id']));
6636            if (isset($evaluationChecker['AdminStaffEvaluation']['rate'])) {
6637                return json_encode(array('success' => true, 'exist' =>  true, 'rate' => $evaluationChecker['AdminStaffEvaluation']['rate']));
6638            } else {
6639                return $this->AdminStaffEvaluation->evaluate($saveData);
6640            }
6641        }
6642        return json_encode($response);
6643    }
6644
6645    /**
6646     * @api {get} /teacher/api/getAnnouncement getAnnouncement()
6647     * @apiName getAnnouncement
6648     * @apiGroup API
6649     * @apiDescription Get the announcement for the teacher.
6650     * @apiSampleRequest off
6651     * 
6652     * @apiBody {String} id The ID of the teacher.
6653     *
6654     * @apiSuccess {Array} announcement The list of announcements.
6655     * 
6656     * @apiErrorExample {json} Success-Response:
6657     *     {
6658     *       "announcement": [...]
6659     *     }
6660     *
6661      * @apiErrorExample {js} Used in: AngularJS
6662      * Location: "webroot/js/ng/home.js"
6663     */
6664    public function getAnnouncement($id) {
6665        $this->autoRender = false;
6666
6667        if (!isset($id) || !ctype_digit($id)){
6668            return false;
6669        }
6670
6671        $teacher = $this->Teacher->find('first', array(
6672            'conditions' => array('Teacher.id' => $id),
6673            'recursive' => -1
6674            )
6675        );
6676
6677        if($teacher){
6678            $now = date('Y-m-d H:i:00');
6679            $type = $teacher['Teacher']['rank_coin_id'];
6680            $id = $teacher['Teacher']['id'];
6681            $home_flg = $teacher['Teacher']['home_flg'];
6682            
6683            $teacherGroupId = $this->TeacherGroupLink->query("SELECT DISTINCT link_id from teacher_groups_links where status = 1 and type = 1 and teacher_id = $id");
6684            $groupIds = array();
6685            $query = array();
6686            if(!empty($teacherGroupId[0]['teacher_groups_links']['link_id'])) {
6687                foreach ($teacherGroupId as $g_id) {
6688                    $groupIds[] = $g_id['teacher_groups_links']['link_id'];    
6689                }
6690                $query[] = array('TeacherAnnouncement.id IN' => $groupIds);
6691            }
6692
6693            $this->TeacherAnnouncement->openDBReplica();
6694            $announcement = $this->TeacherAnnouncement->find('all', array(
6695                'conditions' => array(
6696                    'TeacherAnnouncement.start_time <=' => $now,
6697                    'TeacherAnnouncement.end_time >=' => $now,
6698                    'TeacherAnnouncement.display' => 1,
6699                    'TeacherAnnouncement.notif_type' => 1,
6700                    'TeacherAnnouncementsRead.id' => NULL,
6701                    'OR' => array(
6702                        array('FIND_IN_SET('.$type.', `TeacherAnnouncement.teacher_type`)'),
6703                        array('FIND_IN_SET('.$id.', `TeacherAnnouncement.teacher_id`)'),
6704                        array('FIND_IN_SET('.$type.', `TeacherAnnouncement.teacher_group_type`)'),
6705                        array('TeacherAnnouncement.target_teacher IN' => array(2, $home_flg)),
6706                        $query
6707                    ),    
6708                ),
6709                'joins' => array(
6710                    array(
6711                        'table' => 'teacher_announcements_read',
6712                        'alias' => 'TeacherAnnouncementsRead',
6713                        'type' => 'LEFT',
6714                        'conditions' => array(
6715                            'TeacherAnnouncementsRead.announcement_id = TeacherAnnouncement.id',
6716                            'TeacherAnnouncementsRead.teacher_id = ' . $id,
6717                        )
6718                    )
6719                ),
6720                'order' => 'TeacherAnnouncement.id DESC',
6721            ));
6722            $this->TeacherAnnouncement->closeDBReplica();
6723
6724            return json_encode($announcement);
6725        }
6726        return false;
6727    }
6728
6729    /**
6730     * @api {post} /teacher/api/getAnnouncementRead getAnnouncementRead()
6731     * @apiName getAnnouncementRead
6732     * @apiGroup API
6733     * @apiDescription Mark the announcement as read.
6734     * @apiSampleRequest off
6735     * 
6736     * @apiBody {String} id The ID of the announcement.
6737     * 
6738     * @apiErrorExample {bool} Success-Response:
6739     * true
6740     * 
6741      * @apiErrorExample {js} Used in: AngularJS
6742      * Location: "webroot/js/ng/app.js"
6743     */
6744    public function getAnnouncementRead() {
6745        $this->autoRender = false;
6746        if($this->request->is('post')){
6747            $post = $this->request->data;
6748            $announcementID = $post['id'];
6749            if(trim($announcementID) != ''){
6750                $data = array(
6751                        'announcement_id' => $announcementID,
6752                        'teacher_id' => $this->Auth->User('id')
6753                    );
6754                $this->TeacherAnnouncementsRead->create();
6755                $this->TeacherAnnouncementsRead->set($data);
6756                if ($this->TeacherAnnouncementsRead->save()) {
6757                    return true;
6758                } else {
6759                    return false;
6760                }
6761            } else {
6762                return false;
6763            }
6764        } else {
6765            return false;
6766        }
6767    }
6768
6769    /**
6770     * @api {get} /teacher/api/getLessonRequest getLessonRequest()
6771     * @apiName getLessonRequest
6772     * @apiGroup API
6773     * @apiDescription Get the lesson request for the teacher.
6774     * @apiSampleRequest off
6775     * 
6776     * @apiBody {String} userId The ID of the user.
6777     */
6778    
6779    public function getLessonRequest() {
6780        $this->autoRender = false;
6781        $data['success'] = false;
6782        $userId = isset($this->request->query['userId']) ? $this->request->query['userId'] : '';
6783        return $this->LessonRequest->getLessonRequest(array('userId' => $userId, 'viewingPage' => 'teacher'));;
6784    }
6785
6786    /**
6787     * @api {post} /teacher/api/translateText translateText()
6788     * @apiName translateText
6789     * @apiGroup API
6790     * @apiDescription Translate the text to the target language.
6791     * @apiSampleRequest off
6792     * 
6793     * @apiBody {String} text The text to be translated.
6794     * @apiBody {String} language The target language for translation.
6795     *
6796     * @apiErrorExample {json} Success-Response:
6797     * Returns myTools::translateText($text, $target_language)
6798     *
6799     * @apiErrorExample {js} Used in: Elements
6800     * Location: "view/Elements/chat_area_js.php"
6801     */
6802    /*
6803    * Translate teacher message from language to english language
6804    * youtube tutorial https://www.youtube.com/watch?v=j6Z3EQRvpq4
6805    * return @translated text
6806    */
6807    public function translateText() {
6808        $this->autoRender = false;
6809        if ($this->request->is('ajax')) {
6810            $text = isset($this->request->data['text']) ? $this->request->data['text']:"";
6811            $target_language = isset($this->request->data['language']) ? $this->request->data['language']:"";
6812
6813            return myTools::translateText($text, $target_language);
6814        }
6815    }
6816
6817    /**
6818     * @api {get} /teacher/api/checkTeacherEptSetting checkTeacherEptSetting()
6819     * @apiName checkTeacherEptSetting
6820     * @apiGroup API
6821     * @apiDescription Check if teacher has EPT setting.
6822     * @apiSampleRequest off
6823     * 
6824     * @apiSuccess {Boolean} showModal The status of the request.
6825     * 
6826     * @apiErrorExample {json} Success-Response:
6827     *     {
6828     *       "showModal": true,
6829     *     }
6830     *
6831     * @apiErrorExample {json} Error-Response:
6832     *     {
6833     *       "showModal": false,
6834     *     }
6835     * 
6836      * @apiErrorExample {js} Used in: AngularJS
6837      * Location: "webroot/js/ng/app.js"
6838
6839     * @apiErrorExample {js} Used in: Elements
6840     * Location: "view/Elements/home_based_expedite_payment_content.php"
6841     */
6842    public function checkTeacherEptSetting(){
6843        $this->autoRender = false;
6844        $response = array('showModal' => false);
6845        $check = $this->Session->read('EPT.teacher_seen_modal');
6846
6847        if(!$check){
6848            $settings = $this->Teacher->getTeacherEptSettings($this->Auth->user('id'));
6849            if($settings['ept_flg'] && !$settings['ept_status']){
6850                $response['showModal'] = true;
6851                $this->Session->write('EPT.teacher_seen_modal', true);
6852            }
6853        }
6854
6855        return json_encode($response);
6856    }
6857
6858    /**
6859     * @api {post} /teacher/api/lessonOnAirRequestLessonTime lessonOnAirRequestLessonTime()
6860     * @apiName lessonOnAirRequestLessonTime
6861     * @apiGroup API
6862     * @apiDescription Get lessonOnAir request lesson time.
6863     * @apiSampleRequest off
6864     * 
6865     * @apiSuccess {Boolean} success The status of the request.
6866     * @apiSuccess {Number} requestLessonTimeSec The request lesson time in seconds.
6867     * @apiSuccess {Number} requestLessonTimeMin The request lesson time in minutes.
6868     * @apiSuccess {Boolean} requestLessonVisible The request lesson visibility.    
6869     * @apiSuccess {String} textBookNameEng The English textbook name.
6870     * @apiSuccess {String} textBookNameJpn The Japanese textbook name.
6871     * @apiSuccess {String} current_textbook_element The current textbook element.
6872     * @apiSuccess {Number} chocotto_camp_lesson_flg The chocotto camp lesson flag.
6873     * 
6874     * @apiErrorExample {json} Success-Response:
6875     *     {
6876     *       "success": true,
6877     *       "requestLessonTimeSec": 1500,
6878     *       "requestLessonTimeMin": 25,
6879     *       "requestLessonVisible": true,
6880     *       "textBookNameEng": "English Textbook Name",
6881     *       "textBookNameJpn": "Japanese Textbook Name",
6882     *       "current_textbook_element": "[ Textbook or Course ] Category Name<br>[ Category ] Subcategory Name<br>[ Chapter ] Chapter Name<br>",
6883     *       "chocotto_camp_lesson_flg": 1
6884     *     }
6885     *
6886     * @apiErrorExample {json} Error-Response:
6887     *     {
6888     *       "success": false,
6889     *       "requestLessonTimeSec": 1500,
6890     *       "requestLessonTimeMin": 25,
6891     *       "requestLessonVisible": false,
6892     *       "textBookNameEng": null,
6893     *       "textBookNameJpn": null,
6894     *       "current_textbook_element": null,
6895     *       "chocotto_camp_lesson_flg": 0
6896     *     }
6897     * 
6898     * @apiErrorExample {js} Used in: Elements
6899     * Location: "view/Elements/chat_area_js.php"
6900     */
6901    /**
6902    * Get lessonOnAir request lesson time
6903    * No Params
6904    * @return mixed - json
6905    */
6906    public function lessonOnAirRequestLessonTime() {
6907        $this->autoRender = false;
6908        $dataResponse = array();
6909        $dataResponse['success'] = false;
6910        $dataResponse['requestLessonTimeSec'] = 1500;
6911        $dataResponse['requestLessonTimeMin'] = 25;
6912        $dataResponse['requestLessonVisible'] = false;
6913        $dataResponse['textBookNameEng'] = null;
6914        $dataResponse['textBookNameJpn'] = null;    
6915        $currentTextbookElement = null;    
6916
6917        $chatHash = isset($this->request->query['chat_hash']) ? $this->request->query['chat_hash'] : '';
6918        $userLocale = (isset($this->request->query['studentLessonLocalizeDir']) && $this->request->query['studentLessonLocalizeDir']) ? $this->request->query['studentLessonLocalizeDir'] : Configure::read('default.user_language');
6919
6920        $teacherId = $this->Auth->user('id');
6921        if (!$this->Auth->user('id')) {
6922            return json_encode($dataResponse);
6923        }
6924        $lessonTime = $this->LessonOnair->find('first', array(
6925            'fields' => array(
6926                'LessonOnair.requested_lesson_time',
6927                'LessonOnair.start_time',
6928                'LessonOnair.end_time',
6929                'LessonOnair.lesson_type',
6930                'LessonOnair.user_id',
6931                'LessonOnair.user_agent',
6932                'LessonOnair.connect_id',
6933                'LessonOnair.chocotto_camp_lesson_flg',
6934                'TextbookCategory.id',
6935                'TextbookCategory.name',
6936                'TextbookCategory.english_name',
6937                'TextbookSubCategory.name',
6938                'Textbook.id',
6939                'Textbook.name',
6940                'Textbook.name_eng',
6941                'Textbook.main_topic_id',
6942                'TextbookSubCategory.id',
6943                'TextbookSubCategory.english_name',
6944                'User.native_language2'
6945            ),
6946            'conditions'=> array(
6947                'LessonOnair.chat_hash' => $chatHash,
6948                'LessonOnair.teacher_id' => $teacherId,
6949                'LessonOnair.connect_id !=' => NULL
6950            ),
6951            'joins' => array(
6952                array(
6953                        'type' => 'LEFT',
6954                        'table' => 'textbook_connects',
6955                        'alias' => 'TextbookConnect',
6956                        'conditions' => 'LessonOnair.connect_id = TextbookConnect.id'
6957                    ),
6958                array(
6959                    'type' => 'LEFT',
6960                    'table' => 'textbook_categories',
6961                    'alias' => 'TextbookCategory',
6962                    'conditions' => 'TextbookConnect.category_id = TextbookCategory.id'
6963                ),
6964                array(
6965                    'type' => 'LEFT',
6966                    'table' => 'textbook_subcategories',
6967                    'alias' => 'TextbookSubCategory',
6968                    'conditions' => 'TextbookConnect.subcategory_id = TextbookSubCategory.id'
6969                ),
6970                array(
6971                    'type' => 'LEFT',
6972                    'table' => 'textbooks',
6973                    'alias' => 'Textbook',
6974                    'conditions' => 'Textbook.id = TextbookConnect.textbook_id'
6975                ),
6976                array(
6977                    'type' => 'LEFT',
6978                    'table' => 'users',
6979                    'alias' => 'User',
6980                    'conditions' => 'User.id = LessonOnair.user_id'
6981                )
6982
6983            ),
6984            'order' =>array('LessonOnair.id' => 'DESC'),
6985            'recursive' => -1
6986        ));
6987
6988        // ~ get teacher information
6989        $this->Teacher->openDBReplica();
6990        $teacherData = $this->Teacher->find('first', array(
6991            'fields' => ['avatar_id'],
6992            'conditions' => array('Teacher.id' => $teacherId),
6993            'recursive' => -1
6994        ));
6995        $this->Teacher->closeDBReplica();
6996        $teacherData = new TeacherTable($teacherData['Teacher']);
6997
6998        if (!$lessonTime['LessonOnair']['requested_lesson_time']) {
6999            return json_encode($dataResponse);
7000        }
7001
7002        $pcDeviceType = "PC";
7003        //get user device
7004        $studentDeviceType = myTools::getUsersDevice($lessonTime['LessonOnair']['user_agent']); 
7005        //for mobile lesson
7006        //if device type is not a PC use student language to get the global translation
7007        if ((isset($studentDeviceType) && $studentDeviceType) && $studentDeviceType != $pcDeviceType) {
7008            $userLocale = (isset($lessonTime['User']['native_language2']) && $lessonTime['User']['native_language2']) ? $lessonTime['User']['native_language2'] : 'en';
7009        }
7010
7011        $dataResponse['success'] = true;
7012        $dataResponse['requestLessonTimeSec'] = $lessonTime['LessonOnair']['requested_lesson_time'];
7013        $dataResponse['requestLessonTimeMin'] = ($dataResponse['requestLessonTimeSec']/60);
7014        $dataResponse['requestLessonTimeMinNotification'] = $dataResponse['requestLessonTimeMin'];
7015        if ($lessonTime['LessonOnair']['lesson_type'] == 1) {
7016            $dataResponse['requestLessonVisible'] = true;
7017
7018            //NJ-1446: set the actual seconds including 1min allowance
7019            $_requestedLessonSeconds = (int) $lessonTime['LessonOnair']['requested_lesson_time'] + 60;
7020
7021            //NJ-1446: fetch the difference  
7022            $_differenceLessonRequest = strtotime($lessonTime['LessonOnair']['end_time']) - strtotime($lessonTime['LessonOnair']['start_time']);
7023
7024            //NJ-1446 check if need to adjust the notified requested lesson time 
7025            if ($_requestedLessonSeconds > (int) $_differenceLessonRequest) {
7026                //adjust the requested time 
7027                $_adjustedRequestNotification = ($_differenceLessonRequest / 60);
7028                $_adjustedRequestNotification = (int) $_adjustedRequestNotification;
7029
7030                $dataResponse['requestLessonTimeMinNotification'] = $_adjustedRequestNotification;
7031            }
7032        }
7033
7034        $memcached = new myMemcached();
7035        $textbookNamesCached = $memcached->get(Configure::read('textbook_names_cache_key'));
7036        $order = (isset($textbookNamesCached[$lessonTime['LessonOnair']['connect_id']]['order']) && $textbookNamesCached[$lessonTime['LessonOnair']['connect_id']]['order']) ? $textbookNamesCached[$lessonTime['LessonOnair']['connect_id']]['order'] : '';
7037
7038
7039        // GET GLOBAL TRANSLATION OF TEXTBOOK, CATEGORY AND SUBCATEGORY DATA
7040        $glTextbook = ClassRegistry::init('GlobalTextbookTable')->getGlobalData($lessonTime['Textbook']['id']);
7041        $glTextbookSubcategory = ClassRegistry::init('GlobalTextbookSubcategoryTable')->getGlobalData($lessonTime['TextbookSubCategory']['id']);
7042        $glTextbookCategory = ClassRegistry::init('GlobalTextbookCategoryTable')->getGlobalData($lessonTime['TextbookCategory']['id']);
7043
7044        $textBookChapterName = ($userLocale != Configure::read('default.user_language')) ? ( ($userLocale != 'en') ? ( (isset($glTextbook[$userLocale]['gl_name']) && $glTextbook[$userLocale]['gl_name']) ? $glTextbook[$userLocale]['gl_name'] : $lessonTime['Textbook']['name_eng'] ) : $lessonTime['Textbook']['name_eng'] ) : $lessonTime['Textbook']['name'];
7045        $textBookChapterNameEng = ( isset($lessonTime['Textbook']['name_eng']) && $lessonTime['Textbook']['name_eng'] ) ? $lessonTime['Textbook']['name_eng'] : ( (isset($glTextbook['en']['gl_name']) && $glTextbook['en']['gl_name']) ? $glTextbook['en']['gl_name'] : '' );
7046
7047        $textbookClassName = ($userLocale != Configure::read('default.user_language')) ? ( ($userLocale != 'en') ? ( (isset($glTextbookSubcategory[$userLocale]['gl_name']) && $glTextbookSubcategory[$userLocale]['gl_name']) ? $glTextbookSubcategory[$userLocale]['gl_name'] : $lessonTime['TextbookSubCategory']['english_name'] ) : $lessonTime['TextbookSubCategory']['english_name'] ) : $lessonTime['TextbookSubCategory']['name'];
7048        $textbookClassNameEng = (isset($lessonTime['TextbookSubCategory']['english_name']) && $lessonTime['TextbookSubCategory']['english_name']) ? $lessonTime['TextbookSubCategory']['english_name'] : ( (isset($glTextbookSubcategory['en']['gl_name']) && $glTextbookSubcategory['en']['gl_name']) ? $glTextbookSubcategory['en']['gl_name'] : '' );
7049
7050        $textbookCategoryName = ($userLocale != Configure::read('default.user_language')) ? ( ($userLocale != 'en') ? ( (isset($glTextbookCategory[$userLocale]['gl_name']) && $glTextbookCategory[$userLocale]['gl_name']) ? $glTextbookCategory[$userLocale]['gl_name'] : $lessonTime['TextbookCategory']['english_name'] ) : $lessonTime['TextbookCategory']['english_name'] ) : $lessonTime['TextbookCategory']['name'];
7051        $textbookCategoryNameEng = (isset($lessonTime['TextbookCategory']['english_name']) && $lessonTime['TextbookCategory']['english_name']) ? $lessonTime['TextbookCategory']['english_name'] : ( (isset($glTextbookCategory['en']['gl_name']) && $glTextbookCategory['en']['gl_name']) ? $glTextbookCategory['en']['gl_name'] : '' );
7052
7053        $dataResponse['textBookName'] = ($textbookClassName || $textBookChapterName) ? $textbookClassName .' - '. $order . $textBookChapterName : null;
7054        $dataResponse['textBookNameEng'] = ($textbookClassNameEng || $textBookChapterNameEng) ? $textbookClassNameEng .' - ' . $textBookChapterNameEng : null;
7055
7056        $dataResponse['category_name'] = $textbookCategoryName;
7057        $dataResponse['category_name_eng'] = $textbookCategoryNameEng;
7058
7059        $categoryNameUse = empty($textbookCategoryNameEng) ? $textbookCategoryName : $textbookCategoryNameEng ;
7060        $subCategoryNameUse = empty($textbookClassNameEng) ? $textbookClassName : $textbookClassNameEng ;
7061        $textbookNameUse = empty($textBookChapterNameEng) ? $textBookChapterName : $textBookChapterNameEng ;
7062        // check for main topic
7063        if ( isset($lessonTime['Textbook']['main_topic_id']) && $lessonTime['Textbook']['main_topic_id'] ) {
7064            $textbookMainTopicNameEn = ClassRegistry::init('Textbook')->getTextbookMainTopicName($lessonTime['Textbook']['id'],Configure::read('default.teacher_timezone_id'));
7065            if ($textbookMainTopicNameEn) { 
7066                $subCategoryNameUse = $textbookMainTopicNameEn;
7067            } else {
7068                $langId = ClassRegistry::init('CountryCode')->getUserLanguageId($userLocale);
7069                $textbookMainTopicName = ClassRegistry::init('Textbook')->getTextbookMainTopicName($lessonTime['Textbook']['id'],$langId);
7070                if ($textbookMainTopicName) { 
7071                    $subCategoryNameUse = $textbookMainTopicNameEn; 
7072                }
7073            }
7074        }
7075        if ( $categoryNameUse && $subCategoryNameUse && $textbookNameUse ) {
7076
7077            $currentTextbookElement .= '[ Textbook or Course ] ' . $categoryNameUse . '<br>';
7078            $currentTextbookElement .= '[ Category ] ' . $subCategoryNameUse . '<br>';
7079            $currentTextbookElement .= '[ Chapter ] ' . $order . $textbookNameUse . '<br>';
7080
7081            $dataResponse['current_textbook_element'] = $currentTextbookElement;
7082        }
7083
7084        // - NJ-27262, Chocotto Lesson Flg
7085        $dataResponse['chocotto_camp_lesson_flg'] = $lessonTime['LessonOnair']['chocotto_camp_lesson_flg'];
7086
7087        return json_encode($dataResponse);
7088    }
7089    
7090    /**
7091     * @api {post} /teacher/api/getDateStatus getDateStatus()
7092     * @apiName getDateStatus
7093     * @apiGroup API
7094     * @apiDescription Get the information about the certain slot.
7095     * @apiSampleRequest off
7096     * 
7097     * @apiBody {String} teacherId The ID of the teacher.
7098     * @apiBody {String} dateTime The date and time.
7099     * 
7100     * @apiSuccess {Number} reservedStudent The number of reserved students.
7101     * @apiSuccess {Number} lessonRequentCount The number of lesson requests.
7102     * @apiSuccess {Number} mealBreakCount The number of meal breaks.
7103     * @apiSuccess {Number} slotCount The number of slots.
7104     * @apiSuccess {String} currentServerTime The current server time.
7105     * 
7106     * 
7107     * @apiErrorExample {json} Success-Response:
7108     *     {
7109     *       "reservedStudent": 5,
7110     *       "lessonRequentCount": 3,
7111     *       "mealBreakCount": 2,
7112     *       "slotCount": 4,
7113     *       "currentServerTime": "2023-10-01"
7114     *     }
7115     *
7116     * @apiErrorExample {json} Error-Response:
7117     *     {
7118     *       "reservedStudent": 0,
7119     *       "lessonRequentCount": 0,
7120     *       "mealBreakCount": 0,
7121     *       "slotCount": 0,
7122     *       "currentServerTime": "2023-10-01"
7123     *     }
7124     * 
7125      * @apiErrorExample {js} Used in: AngularJS
7126     * Location: "webroot/js/ng/controller/schedule.js"
7127     */
7128    /**
7129    * NC6202 - get the information about the certain slot
7130    * @param
7131    * @return
7132    */
7133    public function getDateStatus() {
7134        $this->autoRender = false;
7135        $this->layout = false;
7136
7137        $params = $this->request['data'];
7138        $teacherId = $params['teacherId'];
7139        $dateTime = date('Y-m-d H:i:s', strtotime($params['dateTime']));
7140
7141        // - if has no timezone information
7142        $this->refreshTeacherTZ();
7143
7144        //count student reserved
7145        $reserved = $this->LessonSchedule->isReservedFromTeacherIdAndReserveTime($teacherId, $dateTime, null);
7146        $resData['reservedStudent'] = $reserved;
7147
7148        //-- count lesson request
7149        $statusType = array(
7150            Configure::read('lesson_schedule.status.lesson_request'),
7151            Configure::read('lesson_schedule.status.request_confirmation')
7152        );
7153        $resData['lessonRequentCount'] = $this->LessonSchedule->isReservedFromTeacherIdAndReserveTime($teacherId, $dateTime, null, $statusType);
7154
7155        //check for mealbreak on teacher
7156        $limitPerDay = $this->ShiftWorkMealBreak->countTeacherMealbreak($teacherId, $dateTime);
7157        $resData['mealBreakCount'] = $limitPerDay;
7158
7159        //counts teacher slot that are reserved for lesson
7160        $slotCount = $this->ShiftWorkOn->countTeacherSlotOn($teacherId, $dateTime, $this->timeDiff);
7161        $resData['slotCount'] = $slotCount;
7162
7163        $resData['currentServerTime'] = date('Y-m-d');
7164        return json_encode($resData);
7165    }
7166        
7167    /**
7168     * @api {post} /teacher/api/checkFRSendSlack checkFRSendSlack()
7169     * @apiName checkFRSendSlack
7170     * @apiGroup API
7171     * @apiDescription Check if the face recognition should send a slack message.
7172     * @apiSampleRequest off
7173     * 
7174     * @apiBody {String} chatHash The chat hash.
7175     *
7176     * @apiSuccess {Boolean} status The status of the request.
7177     * @apiSuccess {String} msg The message.
7178     * 
7179     * @apiErrorExample {json} Success-Response:
7180     *     {
7181     *       "status": true,
7182     *       "msg": "send message!"
7183     *     }
7184     *
7185     * @apiErrorExample {json} Error-Response:
7186     *     {
7187     *       "status": false,
7188     *       "msg": "nothing to send"
7189     *     }
7190     * 
7191     * @apiErrorExample {js} Used in: Elements
7192     * Location: "view/Elements/chat_area_js.php"
7193     */
7194    public function checkFRSendSlack($chatHash = '') {
7195        $this->autoRender = false;
7196        $response = array('status' => false, 'msg' => 'nothing to send');
7197
7198        $hasRecord = $this->FaceRecognition->find('first', array(
7199            'fields' => array(
7200                'FaceRecognition.id',
7201                'FaceRecognition.image',
7202                'LessonTrackLog.lesson_number',
7203                'FaceRegistration.image',
7204                'User.id',
7205                'User.status',
7206                'User.corporate_id',
7207                'User.charge_flg',
7208                'User.fail_flg',
7209                'User.double_check_flg',
7210                'User.double_check_flg',
7211                'User.parent_id',
7212                'User.payment_plan_id',
7213                'User.hash16',
7214                'User.complimentary_code',
7215                'User.corporate_type',
7216                'User.nickname',
7217                'User.currency_code',
7218                'User.native_language2',
7219                'User.country_code',
7220                '(SELECT COUNT(*) FROM face_recognitions as recog WHERE recog.chat_hash = "'.$chatHash.'" AND recog.mismatch_flg = 0) as hasMatch',
7221                'User.studysapuri_id'
7222            ),
7223            'conditions' => array(
7224                'FaceRecognition.chat_hash' => $chatHash,
7225                'FaceRecognition.mismatch_flg !=' => 2
7226            ),
7227            'joins' => array(
7228                array(
7229                    'type' => 'LEFT',
7230                    'table' => 'lesson_track_logs',
7231                    'alias' => 'LessonTrackLog',
7232                    'conditions' => 'LessonTrackLog.chat_hash = FaceRecognition.chat_hash'
7233                ),
7234                array(
7235                    'type' => 'LEFT',
7236                    'table' => 'users',
7237                    'alias' => 'User',
7238                    'conditions' => 'User.id = FaceRecognition.user_id'
7239                ),
7240                array(
7241                    'type' => 'LEFT',
7242                    'table' => 'face_registrations',
7243                    'alias' => 'FaceRegistration',
7244                    'conditions' => 'FaceRegistration.user_id = FaceRecognition.user_id AND FaceRegistration.id = FaceRecognition.registered_image_id AND FaceRegistration.teacher_flg = 0'
7245                )
7246            ),
7247            'order' => 'FaceRecognition.id DESC'
7248        ));
7249        $this->log("[FR_LESSONFINISH] " . json_encode($hasRecord), 'debug');
7250
7251        if ($hasRecord && isset($hasRecord[0]['hasMatch']) && !$hasRecord[0]['hasMatch']) {
7252            $user = $hasRecord['User'];
7253            $userId = $user['id'];
7254            $faceRecog = $hasRecord['FaceRecognition'];
7255            $faceReg = $hasRecord['FaceRegistration'];
7256            $lessonLog = $hasRecord['LessonTrackLog'];
7257
7258            $membershipType = UserTable::getEngMembershipTypeData();
7259            $userObj = new UserTable($user);
7260            $userMembType = $userObj->getMembershipTypeIndex();
7261            $suddenLesson = $this->User->getSuddenLessonCount($user['id']);
7262            $zeroStudentDiscountOptionInUse = 1;
7263            $zeroStudentStatus = "OFF";
7264
7265            // connect to aws
7266            $s3Client = new AwsFileServer();         
7267            // get filename 
7268            $faceRecogFileName = basename($faceRecog['image']);                    
7269            $faceRegFileName = basename($faceReg['image']);
7270            $discountOptionTerm = $this->UserDiscountOptionsTerm->getTerm([
7271                'user_id' =>  $userId,
7272                'discount_option_id' => Configure::read('discount_option.zero_student.plan_id'),
7273                'status' => 1
7274            ]);
7275            // if zero student discount option user
7276            if ($discountOptionTerm && $discountOptionTerm['discount_option_id'] == Configure::read('discount_option.zero_student.plan_id')) {
7277                $zeroStudentDiscountOptionInUse = 1;
7278                $zeroStudentStatus = "ON";
7279            }
7280
7281            #6796 set default last 30 day from today
7282            $startDateFromToday = strtotime('-29 days', time());
7283            $defaultDate = date('M d, Y (D)', $startDateFromToday) . ' - ' . date('M d, Y (D)');
7284
7285            $text = "@group-cs \n";
7286            $text .= "```種別: 顔認証不一致通知" . "\n";
7287            $text .= "URL: " . myTools::getUrl() . "/admin/face-recognition?recognition_id={$faceRecog['id']}&lesson_id={$lessonLog['lesson_number']} \n";
7288            $text .= "会員名:{$user['nickname']} \n";
7289            $text .= "会員ID:" . "<" . myTools::getBaseUrl() . "/admin/user-manage/member/" . $userId . "|" . $userId . ">" . " \n";
7290            $text .= "ゼロ学割:" . $zeroStudentStatus . " \n";
7291            if($zeroStudentDiscountOptionInUse) {
7292                $text .= "ゼロ学割承認画面URL: " . myTools::getUrl() . "admin/student-discount-approval-screen?userId={$userId}&nickname={$user['nickname']} \n";
7293            }
7294            $text .= "通貨 : {$user['currency_code']} \n";
7295            $text .= "言語: {$user['native_language2']} \n";
7296            $text .= "直近30日の今すぐレッスン受講数:{$suddenLesson}回 \n";
7297            #6796 add lessionUnitPrice,link message-manage,link lesson-history
7298            $text .= "レッスン単価: {$this->lessionUnitPrice($user)} \n";
7299            $text .= "<" . myTools::getBaseUrl() . "/admin/message-manage?user_id={$userId}&display_flg=all&start_time={$defaultDate}&sent_time={$defaultDate}" . "|" . "メッセージ履歴" . ">" . " \n";
7300            $text .= "<" . myTools::getBaseUrl() . "/admin/lesson-history?user_id={$userId}&date={$defaultDate}" . "|" . "レッスン履歴" . ">" . " \n";
7301            $text .= "会員種別:{$membershipType[$userMembType]} \n";
7302            $text .= "SMS国コード:{$user['country_code']} \n";
7303
7304            // NC-10020: show family plan ids
7305            // find Family account by $parentId
7306
7307            $userIdIsParentId = false;
7308
7309            // incase of user is child and have parent_id;
7310            if ($user['parent_id']) {
7311                $parentId = $user['parent_id'];
7312            } else {
7313                // user dont have parent_id, user may be parent
7314                $parentId = $userId;
7315                $userIdIsParentId = true;
7316            }
7317
7318            // get all child and exclude current user id
7319            $allChild = $this->FamilyPlanList->find('all', array(
7320                    'fields' => array('FamilyPlanList.family_id'),
7321                    'conditions' => array(
7322                        'FamilyPlanList.parent_id' => $parentId,
7323                        'FamilyPlanList.approve_flg' => 1
7324                    ),
7325                    'order' => array('FamilyPlanList.created DESC')
7326                ));
7327
7328            // child array is exists, process send linked parent and child id to slack
7329            if (count($allChild) > 0) {
7330                $text .= "関連する親/子ID一覧:\n";
7331
7332                // check whether current user is parent or not
7333                // if user is parent, dont show parent Id (redundant information, we have show userId above)
7334                if (!$userIdIsParentId) {
7335                    // user is not parent id, show parent_id
7336                    $text .= "親ID:" . "<" . myTools::getBaseUrl() . "/admin/user-manage/member/" . $parentId . "|" . $parentId . ">" . " \n";
7337                };
7338
7339                foreach ($allChild as $child) {
7340                    $chidlId = $child['FamilyPlanList']['family_id'];
7341                    if($chidlId !== $userId) {
7342                        $text .= "子ID:" . "<" . myTools::getBaseUrl() . "/admin/user-manage/member/" . $chidlId  . "|" . $chidlId  . ">" . " \n";
7343                    }
7344                }
7345            }
7346
7347            $text .= "```\n";
7348
7349            $blocks = array(
7350                array(
7351                    "type"=> "section",
7352                    "text" => array(
7353                        "type"=> "mrkdwn",
7354                        "text"=> $text
7355                    )
7356                ),
7357                array(
7358                    "type" => "divider"
7359                ),
7360                array(
7361                    "type"=> "section",
7362                    "text" => array(
7363                        "type"=> "mrkdwn",
7364                        "text"=> "元顔画像:"
7365                    ),
7366                    "accessory" => array(
7367                        "type" => "image",
7368                        "image_url" => $s3Client->getPreSignedUrl($faceRegFileName, false),
7369                        "alt_text" => "元顔画像"
7370                    )
7371                ),
7372                array(
7373                    "type" => "section",
7374                    "text" => array(
7375                        "type" => "mrkdwn",
7376                        "text"=> "本レッスンでの顔画像:"
7377                    ),
7378                    "accessory" => array(
7379                        "type" => "image",
7380                        "image_url" => $s3Client->getPreSignedUrl($faceRecogFileName, false),
7381                        "alt_text" => "本レッスンでの顔画像"
7382                    )
7383                )
7384
7385            );
7386
7387            $mySlack = new mySlack();
7388            $mySlack->channel = myTools::checkChannel("#nc-face-recognition", "#fdc-test-channel");
7389            $mySlack->link_names = true;
7390            $mySlack->blocks = $blocks;
7391            $mySlack->username = "Face Recognition";
7392            $mySlack->sendSlack();
7393
7394            $this->log("[FR_SENDSLACK] lesson_id=" . $lessonLog['lesson_number'], 'debug');
7395            $response = array('status' => true, 'msg' => 'send message!');
7396        }
7397
7398        return json_encode($response);
7399    }
7400
7401    /**
7402    * #6796 Caculate lessionUnitPrice
7403    * @return number $lessionUnitPrice
7404    */
7405    private function lessionUnitPrice($user) {
7406        $lessionUnitPrice = "";
7407        $currencyOptions = CurrencyTable::getCurrencyList();
7408        $getsumAmount = $this->Payment->useReplica()->find('first',array(
7409            'fields' => array(
7410                'sum(Payment.amount) as sum_amount'),
7411            'conditions' => array(
7412                'Payment.user_id' => $user['id'],
7413                'Payment.param2' => '')
7414        ));
7415        $sumAmount = isset($getsumAmount[0]['sum_amount']) ? $getsumAmount[0]['sum_amount'] : 0;
7416
7417        # Number of lesson
7418        $lessonNumber = $this->LessonOnairsLog->useReplica()->find('count',array(
7419            'conditions' => array(
7420                'LessonOnairsLog.user_id' => $user['id'],
7421            )
7422        ));
7423        if($sumAmount != 0 && $lessonNumber != 0){
7424            $lessionUnitPrice = (isset($currencyOptions[$user['currency_code']]) ? $currencyOptions[$user['currency_code']] : '') . " " . floor($sumAmount/$lessonNumber) . " / Lesson";
7425        }
7426        return $lessionUnitPrice;
7427    }
7428
7429    /**
7430     * @api {post} /teacher/api/studentYearlySpeakingTest studentYearlySpeakingTest()
7431     * @apiName studentYearlySpeakingTest
7432     * @apiGroup API
7433     * @apiDescription Get student speaking test data for the whole year.
7434     * @apiSampleRequest off
7435     * 
7436     * @apiBody {String} userId The ID of the user.
7437     * @apiBody {String} queryYear The year.
7438     * @apiBody {String} testType The type of the test.
7439     * 
7440     * @apiSuccess {Array} data The data (`UsersMonthlyDiagnosis->studentYearlySpeakingTestSummary($userId, $queryYear, $testType)`).
7441     * 
7442     * @apiErrorExample {json} Success-Response:
7443     *     {
7444     *       "data": [...]
7445     *     }
7446     *
7447      * @apiErrorExample {js} Used in: AngularJS
7448      * Location: "webroot/js/ng/controller/student_info.js"
7449     */
7450     /**
7451    * GET student speaking test data for the whole year
7452    */
7453    public function studentYearlySpeakingTest () {
7454        $this->autoRender = false;
7455        $this->layout = false;
7456        $studentYearlySpeakingTest = array();
7457        $params = isset($this->request['data']) ? $this->request['data'] : array();
7458        $userId = (isset($params['userId']) && $params['userId']) ? $params['userId'] : null;
7459        $queryYear = (isset($params['queryYear']) && $params['queryYear']) ? $params['queryYear'] : null;
7460        $testType = (isset($params['testType']) && $params['testType']) ? $params['testType'] : 0;
7461        if (!$userId || !$queryYear) {
7462            return json_encode($studentYearlySpeakingTest);
7463        }
7464        $UsersMonthlyDiagnosis = ClassRegistry::init('UsersChivoxMonthlyDiagnosis');
7465        $studentYearlySpeakingTest = $UsersMonthlyDiagnosis->studentYearlySpeakingTestSummary($userId, $queryYear, $testType);
7466        return json_encode($studentYearlySpeakingTest);
7467    }
7468
7469    /**
7470     * @api {get} /teacher/api/uploadRecordedFile uploadRecordedFile()
7471     * @apiName uploadRecordedFile
7472     * @apiGroup API
7473     * @apiDescription Save audio and get equivalent text from Google API.
7474     * @apiSampleRequest off
7475     * 
7476     * @apiBody {String} connect_id The ID of the connect.
7477     * 
7478     * @apiSuccess {Boolean} success The status of the request.
7479     * @apiSuccess {String} convertedSpeech The converted speech.
7480     * @apiSuccess {String} audio_url The audio URL.
7481     * @apiSuccess {String} textbook_name The textbook name.
7482     * 
7483     * @apiErrorExample {json} Success-Response:
7484     *     {
7485     *       "success": true,
7486     *       "convertedSpeech": "Converted text",
7487     *       "audio_url": "url",
7488     *       "textbook_name": "Category Subcategory : Textbook"
7489     *     }
7490     *
7491      * @apiErrorExample {js} Used in: JS
7492      * Location: "webroot/js/custom_sound-recorder.js"
7493     */
7494    /**
7495    * Save audio and get equivalent text from Google API
7496    * @return mixed - json $response
7497    */
7498   public function uploadRecordedFile() {
7499        $this->autoRender = false;
7500
7501        $response = array(
7502            'success' => false,
7503            'convertedSpeech' => '{"jpn" : "〔エラー〕:音声認識に問題があります。ヘッドセットあるいはイヤホンの着用/インターネット環境のご確認をお願いします","eng" : "Error : There is a problem with voice recognition. Please wear a headset or earphones and check your internet environment"}'
7504        );
7505
7506        // - check if request has a valid file
7507        if ((!isset($_FILES['audio_wav']['size']) || !$_FILES['audio_wav']['size']) || (!isset($_FILES['audio_mp3']['size']) || !$_FILES['audio_mp3']['size'])) {
7508            return json_encode($response);
7509        }
7510        
7511        $connectId                 = !empty($this->request->data['connect_id']) ? $this->request->data['connect_id'] : null;
7512        $storedRecordingFlag     = !empty($this->request->data['store_audio_record']) ? true : false;
7513        $userId                    = !empty($this->request->data['user_id']) ? $this->request->data['user_id'] : null;
7514        $questionName            = !empty($this->request->data['question_name']) ? $this->request->data['question_name'] : null;
7515        $questionTitle            = !empty($this->request->data['question_title']) ? $this->request->data['question_title'] : null;
7516
7517        //-- user current language
7518        $userLanguage = $this->User->fetchUserCurrentLanguage(array(
7519            'user_id' => $userId
7520        ));
7521
7522        // - override question name
7523        if (!empty($questionTitle)) {
7524            $questionName = $questionTitle;
7525        }
7526
7527        // - Get audio temporary folder
7528        $audioContainer = ROOT . '/instructor/webroot/sound';
7529        if (!is_dir($audioContainer)) {
7530            mkdir($audioContainer);
7531        }
7532
7533        // - move the file from temp name to local folder using $output name
7534        $wavInput = $_FILES['audio_wav']['tmp_name'];
7535        $mp3Input = $_FILES['audio_mp3']['tmp_name'];
7536
7537        $wavOutput = $audioContainer . '/' . $_FILES['audio_wav']['name'] . '.wav';
7538        $mp3Output = $audioContainer . '/' . $_FILES['audio_mp3']['name'] . '.mp3';
7539
7540        // - delete audio file if already exist
7541        if (file_exists($wavOutput)) {
7542            unlink($wavOutput);
7543        }
7544        if (file_exists($mp3Output)) {
7545            unlink($mp3Output);
7546        }
7547        
7548        // - upload file
7549        $moveFileStatusWav = move_uploaded_file($wavInput, $wavOutput);
7550        $moveFileStatusMp3 = move_uploaded_file($mp3Input, $mp3Output);
7551        $fileUrl = "";
7552
7553        //-- Move audio recordings to s3 
7554        $this->loadModel('FileStorage');
7555        if ($moveFileStatusWav && $moveFileStatusMp3 && !empty($storedRecordingFlag)) {
7556            // - upload file to s3
7557            $wavFilename = time() . uniqid() . '_wav_' . basename($wavOutput);
7558            $mp3Filename = time() . uniqid() . '_mp3_' . basename($mp3Output);
7559
7560            $wavResult = $this->FileStorage->uploadFile([
7561                'uploader_id'  => $this->Auth->user('id'),
7562                'uploader_type' => 34,
7563                'source'       => $wavOutput,
7564                'key'          => $wavFilename,
7565                'file'         => $_FILES['audio_wav'],
7566                'delete_image' => true
7567            ]);
7568            
7569            $mp3Result = $this->FileStorage->uploadFile([
7570                'uploader_id'  => $this->Auth->user('id'),
7571                'uploader_type' => 34,
7572                'source'       => $mp3Output,
7573                'key'          => $mp3Filename,
7574                'file'         => $_FILES['audio_mp3'],
7575                'delete_image' => true
7576            ]);
7577            $fileUrl = $mp3Result['FileStorage']['url'];
7578
7579            // - get audio response
7580            $response['audio_url'] = str_replace('https://', '', $fileUrl);
7581        }
7582
7583        // NC-9388: START GOOGLE API SPEECH TO TEXT PR0CESS
7584        if (isset($wavResult['FileStorage']['url'])) {
7585            App::uses('myRedis', 'Lib');
7586
7587            // - get speech
7588            $responseGoogle = myTools::performGooglSpeechToText([
7589                'audio_url' => $wavResult['FileStorage']['url'],
7590                'user_language' => $userLanguage
7591            ]);
7592            
7593            // - check if success
7594            if ($responseGoogle['success']) {
7595                $response['success'] = true;
7596                $response['convertedSpeech'] = $responseGoogle['convertedSpeech'] ?? '';
7597
7598                if (!empty($responseGoogle['totalTime']) && is_array($responseGoogle['totalTime'])) {
7599                    //get word timestamps
7600                    $sum = 0;
7601                    foreach ($responseGoogle['totalTime'] as $key => $value) {
7602                        foreach ($value as $key2 => $value2) {
7603                            //get each word start and end time
7604                            $startTime = floatval($value2['startTime']);
7605                            $endTime = floatval($value2['endTime']);
7606                            // sum the difference of start and end time
7607                            $sum += (float)($endTime -  $startTime);
7608                        }
7609                    }
7610
7611                    //save speech to text logs
7612                    $logParams = array(
7613                        'service' => 2,
7614                        'type' => 3,
7615                        'controller' => static::class,
7616                        'method' => __METHOD__,
7617                        'url' => $this->request->here(),
7618                        'value'    => number_format($sum, 2, '.', '')
7619                    );
7620                    $googleLogs = ClassRegistry::init('GoogleTranslateLog');
7621                    $result = $googleLogs->saveLog($logParams);
7622                    //check if logs saved
7623                    if (!$result) {
7624                        $this->log('error', __METHOD__ . " -- [Google Translate] Unable to save logs. ");
7625                    }
7626                }
7627            }
7628
7629            $response['textbook_name'] = '';            
7630
7631            $textbookName = $this->TextbookConnect->getTextbookName(array(
7632                'connect_id' => $connectId,
7633                'native_language' => $userLanguage,
7634                'translate_other_lang' => true
7635            ));
7636
7637            $response['textbook_name'] .= !empty($textbookName['textbook_category_name']) ? $textbookName['textbook_category_name'] : '';
7638            $response['textbook_name'] .= !empty($textbookName['textbook_subcategory_name']) ? ' ' . $textbookName['textbook_subcategory_name'] : '';
7639            $response['textbook_name'] .= !empty($textbookName['textbook_name']) ? ' : ' . $textbookName['textbook_name'] : '';
7640            $response['success'] = true;
7641
7642            // instantiate view
7643            $view = new View($this, false);
7644            $view->layout = false;
7645
7646            $viewData = array(
7647                'convertedSpeech' => $response['convertedSpeech'] ?? '',
7648                'isSpeechRecording' => 1,
7649                'question_name' => !empty($questionName) ? $questionName : $textbookName['textbook_name'],
7650                'audio_url' => $fileUrl
7651            ); 
7652
7653            $resultViewStudent = $view->element('student_chat_class_chivox', $viewData);
7654            $viewData['question_name'] = !empty($questionName) ? $questionName : $textbookName['textbook_name_eng'];
7655            $resultViewTeacher = $view->element('teacher_chat_class_chivox', $viewData);
7656
7657            $response['resultViewStudent'] = $resultViewStudent;
7658            $response['resultViewTeacher'] = $resultViewTeacher;
7659        }
7660        // NC-9388: END GOOGLE API SPEECH TO TEXT PR0CESS
7661
7662        // - delete audio file if it exist
7663        if (file_exists($wavOutput)) {
7664            unlink($wavOutput);
7665        }
7666        if (file_exists($mp3Output)) {
7667            unlink($mp3Output);
7668        }
7669        
7670        // - return json encode
7671        return json_encode($response);
7672    }
7673
7674    /**
7675     * @api {post} /teacher/api/uploadRecordedFileChivox uploadRecordedFileChivox()
7676     * @apiName uploadRecordedFileChivox
7677     * @apiGroup API
7678     * @apiDescription Save audio to aws, use to request chivox ai.
7679     * @apiSampleRequest off
7680     * 
7681     * @apiBody {String} chivox_pc The chivox pc.
7682     * @apiBody {String} refText The reference text.
7683     * @apiBody {String} sampleRate The sample rate.
7684     * @apiBody {String} chivox_type The chivox type.
7685     * @apiBody {String} keyword The keyword.
7686     * @apiBody {String} keyletterlocation The key letter location.
7687     * @apiBody {String} userID The ID of the user.
7688     * @apiBody {String} textbookCategory The textbook category.
7689     * @apiBody {String} kernel The kernel.
7690     * @apiBody {String} store_audio_record The store audio record.
7691     * @apiBody {String} connect_id The ID of the connect.
7692     * @apiBody {String} studentLessonLocalizeDir The student lesson localize directory.
7693     * 
7694     * @apiSuccess {Boolean} success The status of the request.
7695     * @apiSuccess {String} resultView The result view.
7696     * @apiSuccess {String} audio_url The audio URL.
7697     * @apiSuccess {String} textbook_name The textbook name.
7698     * 
7699     * @apiErrorExample {json} Success-Response:
7700     *     {
7701     *       "success": true,
7702     *       "resultView": "HTML content",
7703     *       "audio_url": "url",
7704     *       "textbook_name": "Category Subcategory : Textbook"
7705     *     }
7706     *
7707     * @apiErrorExample {json} Error-Response:
7708     *     {
7709     *       "success": false,
7710     *       "error": "Chivox Error!"
7711     *     }
7712     * 
7713      * @apiErrorExample {js} Used in: JS
7714      * Location: "webroot/js/custom_sound-recorder.js"
7715     */
7716    /**
7717    * Save audio to aws, use to request chivox ai
7718    * @return mixed  - json $response
7719    */
7720    public function uploadRecordedFileChivox () {
7721        $this->autoRender = false;
7722        $response['success'] = false;
7723
7724        // - check if request has a valid file
7725        if (
7726            (!isset($_FILES['audio_wav']['size']) || !$_FILES['audio_wav']['size'])
7727            || (!isset($_FILES['audio_mp3']['size']) || !$_FILES['audio_mp3']['size']) 
7728            || empty($this->request->data['refText'])
7729        ) {
7730            return json_encode($response);
7731        }
7732
7733        $qvalue_mp3 = $_FILES['audio_mp3'];
7734        $qvalue_wav = $_FILES['audio_wav'];
7735        $chivox_pc = isset($this->request->data['chivox_pc']) ? $this->request->data['chivox_pc'] : 0;
7736        $refText = isset($this->request->data['refText']) ? $this->request->data['refText'] : "";
7737        $chivoxSampleRate = Configure::read('chivox.sampleRate');
7738        $sampleRate = isset($this->request->data['sampleRate']) && $this->request->data['sampleRate'] <= $chivoxSampleRate ? $this->request->data['sampleRate'] : $chivoxSampleRate;
7739        $chivox_type = isset($this->request->data['chivox_type']) ? $this->request->data['chivox_type'] : 1;
7740        $keyword = isset($this->request->data['keyword']) ? $this->request->data['keyword'] : "";
7741        $keyletterlocation = isset($this->request->data['keyletterlocation']) ? $this->request->data['keyletterlocation'] : 1;
7742        $userID = isset($this->request->data['userID']) ? $this->request->data['userID'] : 0;
7743        $textbookCategory = isset($this->request->data['textbookCategory']) ? $this->request->data['textbookCategory'] : null;
7744        $kernel = isset($this->request->data['kernel']) ? $this->request->data['kernel'] : null;
7745        $storedRecordingFlag = !empty($this->request->data['store_audio_record']) ? true : false;
7746        $connectId = !empty($this->request->data['connect_id']) ?$this->request->data['connect_id'] : 0;
7747        $studentLessonLocalizeDir = isset($this->request->data['studentLessonLocalizeDir']) && !empty($this->request->data['studentLessonLocalizeDir']) ? $this->request->data['studentLessonLocalizeDir'] : null ;
7748        $questionName = !empty($this->request->data['question_name']) ? $this->request->data['question_name'] : "";
7749        $addParams = isset($this->request->data['addParams']) ? json_decode($this->request->data['addParams'], true) : null;
7750        $labelTranslations = $addParams['label_translations'] ?? [];
7751        $chatHash = !empty($this->request->data['chat_hash']) ? $this->request->data['chat_hash'] : "";
7752        $accent = isset($this->request->data['accent']) ? $this->request->data['accent'] : 3; // default is 3
7753        $recitation = isset($this->request->data['recitation']) ? $this->request->data['recitation'] : null;
7754
7755        // - Get audio temporary folder
7756        $audioContainer = ROOT . '/user/webroot/files';
7757        if (!is_dir($audioContainer)) {
7758            mkdir($audioContainer);
7759        }
7760
7761        //upload to amazon server
7762        $filename_mp3 = uniqid() . "_" . $_FILES['audio_mp3']['name'] . '.mp3';
7763        $filename_wav = uniqid() . "_" . $_FILES['audio_wav']['name'] . '.wav';
7764        //temporary location
7765        $tempLocation_mp3 = ROOT . '/user/webroot/files/'. $filename_mp3;
7766        $tempLocation_wav = ROOT . '/user/webroot/files/'. $filename_wav;
7767        // - delete audio file if already exist
7768        if (file_exists($tempLocation_mp3)) {
7769            unlink($tempLocation_mp3);
7770        }
7771        if (file_exists($tempLocation_wav)) {
7772            unlink($tempLocation_wav);
7773        }
7774        move_uploaded_file(
7775            $qvalue_mp3['tmp_name'],
7776            $tempLocation_mp3
7777        );
7778        move_uploaded_file(
7779            $qvalue_wav['tmp_name'],
7780            $tempLocation_wav
7781        );
7782        // note this file is only temporary, so uploader_is is set to user_id only
7783        $uploader_id = $this->Auth->user('id');
7784
7785        $this->loadModel('FileStorage');
7786
7787        //-- upload chatbox audio recording
7788        $chaboxAudRecordUpload = $this->FileStorage->uploadFile(array(
7789            'uploader_id' => $uploader_id,
7790            'uploader_type' => 34,
7791            'source' => $tempLocation_mp3,
7792            'key' => $filename_mp3,
7793            'file' => $_FILES['audio_mp3'],
7794            'delete_image' => false
7795        ));
7796
7797        $resultUpload = $this->FileStorage->uploadFile(array(
7798            'uploader_id' => $uploader_id,
7799            'uploader_type' => 19, // 19 - Chivox file
7800            'source' => $tempLocation_wav,
7801            'key' => $filename_wav,
7802            'file' => $_FILES['audio_wav'],
7803            'delete_image' => true
7804        ));
7805
7806        // if aws audio is sucess request chivox thru java server
7807        if (isset($resultUpload['FileStorage']['url'])) {
7808
7809            // get chivox type
7810            $chivoxCountType = 4;
7811            $pj_identifier =  Configure::read('chivox.identifiers.textbook_speaking_training');
7812            if ($textbookCategory == Configure::read('chivox.textbook_categories.speaking_training')){
7813                $chivoxCountType = 4;
7814                $pj_identifier =  Configure::read('chivox.identifiers.textbook_speaking_training');
7815            } else if ($textbookCategory == Configure::read('chivox.textbook_categories.eiken_course') || $textbookCategory == Configure::read('chivox.textbook_categories.eiken_category')){
7816                $chivoxCountType = 5;
7817                $pj_identifier =  Configure::read('chivox.identifiers.textbook_eiken');
7818            } else if ($textbookCategory == Configure::read('chivox.textbook_categories.basicpronunciation_course') || $textbookCategory == Configure::read('chivox.textbook_categories.basicpronunciation_category')){
7819                $chivoxCountType = 6;
7820                $pj_identifier =  Configure::read('chivox.identifiers.textbook_basic_pronunciation');
7821            } else if ($textbookCategory == Configure::read('chivox.textbook_categories.intonationpronunciation_course') || $textbookCategory == Configure::read('chivox.textbook_categories.intonationpronunciation_category')){
7822                $chivoxCountType = 8;
7823                $pj_identifier =  Configure::read('chivox.identifiers.textbook_intonation_pronunciation');
7824            } else if ($textbookCategory == Configure::read('chivox.textbook_categories.linkingpronunciation_course') || $textbookCategory == Configure::read('chivox.textbook_categories.linkingpronunciation_category')){
7825                $chivoxCountType = 8;
7826                $pj_identifier =  Configure::read('chivox.identifiers.textbook_linking_pronunciation');
7827            } else if ($textbookCategory == Configure::read('chivox.textbook_categories.accentpronunciation_course') || $textbookCategory == Configure::read('chivox.textbook_categories.accentpronunciation_category')){
7828                $chivoxCountType = 8;
7829                $pj_identifier =  Configure::read('chivox.identifiers.textbook_accent_pronunciation');
7830            } else if ($textbookCategory == Configure::read('chivox.textbook_categories.americanaccentpronunciation_course') || $textbookCategory == Configure::read('chivox.textbook_categories.americanaccentpronunciation_category')){
7831                $chivoxCountType = 8;
7832                $pj_identifier =  Configure::read('chivox.identifiers.textbook_american_accent_pronunciation');
7833            } else if ($textbookCategory == Configure::read('chivox.textbook_categories.speaking_training_for_school')){
7834                $chivoxCountType = 13; // NJ-12005 - Speaking Training for School
7835                $pj_identifier =  Configure::read('chivox.identifiers.textbook_speaking_training_for_school');
7836            } else if ($textbookCategory == Configure::read('chivox.textbook_categories.speaking_training_for_business')){
7837                $chivoxCountType = 14; // NJ-1453 - Speaking Training for business
7838                $pj_identifier =  Configure::read('chivox.identifiers.textbook_speaking_training_for_business');
7839            } else if ($textbookCategory == Configure::read('chivox.textbook_categories.practicalpronunciation_training')){
7840                $chivoxCountType = 24; // NJ-30843 - Practical Pronunciation Textbook
7841                $pj_identifier =  Configure::read('chivox.identifiers.textbook_practical_pronunciation');
7842            } else if ($textbookCategory == Configure::read('chivox.textbook_categories.toeicspeakingtest_training')) {
7843                $chivoxCountType = 29; // NJ-38904 - TOEIC Speaking Test
7844                $pj_identifier = Configure::read('chivox.identifiers.textbook_toeic_speaking_test');
7845            } else if ($textbookCategory == Configure::read('chivox.textbook_categories.basicpronunciation_british_course') || $textbookCategory == Configure::read('chivox.textbook_categories.basicpronunciation_british_category')){
7846                $chivoxCountType = 31; // NJ-38904 - Basic Pronunciation British Accent
7847                $pj_identifier =  Configure::read('chivox.identifiers.textbook_basic_pronunciation_british');
7848            }
7849
7850            // environment
7851            $ENV = Configure::read('ENVIRONMENT');
7852            $uri = myTools::getChivoxURI($ENV);
7853            $uri = isset($uri['chivox_utility']) ? $uri['chivox_utility'] : "";
7854
7855            if (in_array($chivox_type, array(2,5,7,8,9,10,11))) {
7856                if ($chivox_type == 5) { // 5: Linking Pronunciation
7857                    $rangeRotalScore = $this->SettingOption->getOptions('chivox_linkingpron_total_score');
7858                    $rangeLetterScore = $this->SettingOption->getOptions('chivox_linkingpron_letter_score');
7859                } else if (in_array($chivox_type, array(7,8,9,10,11))) { // 7: Reduction American Accent || 8:9:10: T-Variation || 11: Shorten American Accent
7860                    $rangeRotalScore = $this->SettingOption->getOptions('chivox_american_total_score');
7861                    $rangeLetterScore = $this->SettingOption->getOptions('chivox_american_letter_score');
7862                    $originalText = explode(" ", $refText);
7863                    $keyText = $originalText[$keyword-1];
7864                    $keyword = str_replace([',', '.', '?'], '', $keyText);
7865                } else { // 2: Basic Pronunciation
7866                    $rangeRotalScore = $this->SettingOption->getOptions('chivox_basicpron_total_score');
7867                    $rangeLetterScore = $this->SettingOption->getOptions('chivox_basicpron_letter_score');
7868                }
7869
7870                $range = array_merge($rangeRotalScore, $rangeLetterScore);
7871
7872                $fields = array(
7873                    'action' => "audioPronunciation",
7874                    'env' => $ENV,
7875                    'filename' => $filename_wav,
7876                    'refText' => $refText,
7877                    'userRecordedAudioUri' => $resultUpload['FileStorage']['url'],
7878                    'sampleRate' => $sampleRate,
7879                    'score_keeptrying_from' => $range['score_keeptrying_from'],
7880                    'score_keeptrying_to' => $range['score_keeptrying_to'],
7881                    'score_good_from' => $range['score_good_from'],
7882                    'score_good_to' => $range['score_good_to'],
7883                    'score_excellent_from' => $range['score_excellent_from'],
7884                    'score_excellent_to' => $range['score_excellent_to'],
7885                    'score_perfect_from' => $range['score_perfect_from'],
7886                    'score_perfect_to' => $range['score_perfect_to'],
7887                    'word_point_green_from' => $range['word_point_green_from'],
7888                    'word_point_green_to' => $range['word_point_green_to'],
7889                    'word_point_red_from' => $range['word_point_red_from'],
7890                    'word_point_red_to' => $range['word_point_red_to'],
7891                    'kernel' => $kernel,
7892                    'keyword' => $keyword,
7893                    'user_id' => $userID,
7894                    'pj_identifier' => $pj_identifier
7895                );
7896                if ($chivox_type == 5) {
7897                    $fields['result_view'] = 'linking_score';
7898                } else if (in_array($chivox_type, array(8,9,10))) {
7899                    $fields['result_view'] = 'tvariation_score';
7900                }
7901                $this->log(json_encode($fields), 'debug');
7902            } else if ($chivox_type == 4 || $chivox_type == 14) {
7903                $fields = array(
7904                    'action' => "audioPronunciation",
7905                    'env' => $ENV,
7906                    'filename' => $filename_wav,
7907                    'refText' => $refText,
7908                    'userRecordedAudioUri' => $resultUpload['FileStorage']['url'],
7909                    'sampleRate' => $sampleRate,
7910                    'kernel' => $kernel,
7911                    'keyword' => $keyword,
7912                    'keyletterlocation' => $keyletterlocation,
7913                    'user_id' => $userID,
7914                    'pj_identifier' => $pj_identifier,
7915                    'result_view' => 'stress_score'
7916                );
7917
7918                // NJ-8445: temp fix before actual chivox side fix
7919                $phoneme = array('architecture','centimeter','competition', 'investment', 'mobile', 'motivation', 'politician', 'supermarket', 'yoghurt', 'popular');
7920                if (in_array($refText, $phoneme)) {
7921                    $fields['phoneme'] = 1;
7922                }
7923
7924                $this->log(json_encode($fields), 'debug');
7925            } else if ($chivox_type == 6) {
7926                $getIntonation = explode('-', json_decode($keyword));
7927                $getWords = explode(' ', $refText);
7928                $newRefText = array();
7929                if (!empty($getWords)) {
7930                    foreach ($getWords as $key => $value) {
7931                        if (in_array($key+1, $getIntonation)) {
7932                            $newValue = str_replace(',', '', $value);
7933                            $newValue = str_replace('?', '', $newValue);
7934                            $newRefText[] = $newValue."(s:1)";
7935                        } else {
7936                            $newRefText[] = $value;
7937                        }
7938                    }
7939                }
7940                $newRefText = implode(" ", $newRefText);
7941                $fields = array(
7942                    'action' => "audioPronunciation",
7943                    'env' => $ENV,
7944                    'filename' => $filename_wav,
7945                    'refText' => $newRefText,
7946                    'userRecordedAudioUri' => $resultUpload['FileStorage']['url'],
7947                    'sampleRate' => $sampleRate,
7948                    'kernel' => $kernel,
7949                    'keyword' => json_decode($keyword),
7950                    'keyletterlocation' => json_decode($keyletterlocation),
7951                    'user_id' => $userID,
7952                    'pj_identifier' => $pj_identifier,
7953                    'result_view' => 'intonation_score'
7954                );
7955                $this->log(json_encode($fields), 'debug');
7956            } else {
7957                $fields = array(
7958                    'action' => "audio",
7959                    'env' => $ENV,
7960                    'filename' => $filename_wav,
7961                    'refText' => $refText,
7962                    'userRecordedAudioUri' => $resultUpload['FileStorage']['url'],
7963                    'sampleRate' => $sampleRate,
7964                    'user_id' => $userID,
7965                    'pj_identifier' => $pj_identifier
7966                );
7967
7968                if (!empty($recitation)) {
7969                    $fields['recitation'] = $recitation;
7970                }
7971
7972                if ($kernel == 'en.pred.score') {
7973                    $fields['action'] = 'audioParagraph';
7974                }
7975            }
7976
7977            //NJ-37185: add accent
7978            if (in_array($kernel, ['en.word.pron', 'en.sent.pron']) && $accent == 1) {
7979                $fields['accent'] = $accent;
7980            }
7981            //NJ-36647: add device type
7982            $fields['device_type'] = "PC";
7983            $fields_string = "";
7984
7985            //url-ify the data for the POST
7986            foreach($fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; }
7987            rtrim($fields_string, '&');
7988
7989            $ch = curl_init($uri);
7990            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
7991            curl_setopt($ch, CURLOPT_HEADER, false);
7992            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
7993            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
7994
7995            curl_setopt($ch,CURLOPT_POST, count($fields));
7996            curl_setopt($ch,CURLOPT_POSTFIELDS, $fields_string);
7997
7998            $result = curl_exec($ch);
7999            curl_close($ch);
8000            $result = json_decode($result, true);
8001            
8002            // - NJ-3658 response error code
8003            $response['chivoxError'] = isset($result['rawScore']['errId'])? $result['rawScore']['errId'] : null;
8004            
8005            if (isset($result['success']) && $result['success'] && $result['rawScore']['errId'] != 41030) {
8006
8007                $response = $result;
8008                $this->log("[NC-9473] java response " . json_encode($response), 'debug');
8009
8010                // instantiate view
8011                $view = new View($this, false);
8012                $view->layout = false;
8013                
8014                $viewFile = 'reading_ai_onlesson_result';
8015                // default view data 
8016                $viewData = array(
8017                    'details' => $result['details'],
8018                    'scoreValue' => $result['scoreValue'],
8019                    'refText' => $refText,
8020                    'chivox_pc' => $chivox_pc,
8021                    'chivox_type' => $chivox_type
8022                );                
8023                
8024                if ($chivox_type == 2) {
8025                    $viewData['keyword'] = $keyword;
8026                    $viewData['keyletterlocation'] = $keyletterlocation;
8027                    $viewData['average_score'] = isset($result['average_score']) ? $result['average_score'] : '';
8028                    $viewFile = 'pronunciation_basics_onlesson_result';
8029                } else if ($chivox_type == 4 || $chivox_type == 14) {
8030                    $viewData['keyword'] = $keyword;
8031                    $viewData['keyletterlocation'] = $keyletterlocation;
8032                    $viewData['label_translations'] = $labelTranslations;
8033                    $viewFile = 'pronunciation_advance_onlesson_result';
8034                } else if ($chivox_type == 5) {
8035                    $viewData['keyword'] = $keyword;
8036                    $viewData['keyletterlocation'] = $keyletterlocation;
8037                    $viewData['average_score'] = isset($result['average_score']) ? $result['average_score'] : '';
8038                    $viewData['score_keeptrying_to'] = isset($range['score_keeptrying_to']) ? $range['score_keeptrying_to'] : 30;
8039                    $viewFile = 'pronunciation_linking_onlesson_result';
8040                } else if ($chivox_type == 6) {
8041                    $viewData['keyword'] = json_decode($keyword);
8042                    $viewData['keyletterlocation'] = json_decode($keyletterlocation);
8043                    $viewData['label_translations'] = $labelTranslations;
8044                    $viewFile = 'pronunciation_intonation_onlesson_result';
8045                } else if (in_array($chivox_type, array(7,8,9,10,11))) {
8046                    $viewData['keyword'] = $keyword;
8047                    $viewData['keyletterlocation'] = $keyletterlocation;
8048                    $viewData['average_score'] = isset($result['average_score']) ? $result['average_score'] : '';
8049                    $viewData['score_keeptrying_to'] = isset($range['score_keeptrying_to']) ? $range['score_keeptrying_to'] : 30;
8050                    $viewData['word_point_red_to'] = isset($range['word_point_red_to']) ? $range['word_point_red_to'] : 50;
8051                    $viewFile = 'pronunciation_usa_onlesson_result';
8052                } else {
8053                    $view->set(array(
8054                        'details' => $result['details'],
8055                        'scoreValue' => $result['scoreValue'],
8056                        'refText' => $refText,
8057                        'chivox_pc' => $chivox_pc,
8058                        'chivox_type' => $chivox_type,
8059                        "phonemes" => $phonemes                    
8060                    ));
8061                    $resultView = $view->element('reading_ai_onlesson_result');
8062                }
8063                
8064                if ( !empty($storedRecordingFlag) ) {
8065                    $fileUrl = $chaboxAudRecordUpload['FileStorage']['url'];
8066                    $response['audio_url'] = str_replace('https://', '', $fileUrl);
8067                    $response['textbook_name'] = '';
8068                    //-- user current language
8069                    $userLanguage = $this->User->fetchUserCurrentLanguage(array(
8070                        'user_id' => $userID,
8071                        'studentLessonLocalizeDir' => $studentLessonLocalizeDir
8072                    ));        
8073                    $textbookName = $this->TextbookConnect->getTextbookName(array(
8074                        'connect_id' => $connectId,
8075                        'native_language' => $userLanguage,
8076                        'translate_other_lang' => true
8077                    ));                    
8078                    $response['textbook_name'] .= !empty($textbookName['textbook_category_name']) ? $textbookName['textbook_category_name'] : '';
8079                    $response['textbook_name'] .= !empty($textbookName['textbook_subcategory_name']) ? ' ' . $textbookName['textbook_subcategory_name'] : '';
8080                    $response['textbook_name'] .= !empty($textbookName['textbook_name']) ? ' : ' . $textbookName['textbook_name'] : '';
8081
8082                    $viewData['audio_url'] = isset($chaboxAudRecordUpload['FileStorage']['url']) ? $chaboxAudRecordUpload['FileStorage']['url'] : "";
8083                    $viewData['question_name'] = !empty($questionName) ? $questionName : $textbookName['textbook_name'];
8084
8085                    if (in_array($chivox_type, [4, 6, 14])) {
8086                        $studentDeviceType = "";
8087
8088                        if (!empty($chatHash)) {
8089                            $this->LessonOnair->openDBReplica();
8090                            $userAgent = $this->LessonOnair->field('user_agent', array('LessonOnair.chat_hash' => $chatHash));
8091                            $this->LessonOnair->closeDBReplica();
8092                            
8093                            $studentDeviceType = myTools::getUsersDevice($userAgent);
8094                        }
8095
8096                        $viewData['label_translations'] = ($studentDeviceType != 'PC')
8097                            ? $this->getLabelTranslations($userLanguage)
8098                            : $labelTranslations;
8099
8100                        $this->log("users device and language: " . $studentDeviceType . " - " . $userLanguage, 'debug');
8101                        $this->log("label translations: " . json_encode($viewData['label_translations']), 'debug');
8102                    }
8103                }
8104
8105                // - old design result
8106                $resultView = $view->element($viewFile, $viewData);
8107                $response['resultView'] = $resultView;
8108                
8109                $resultViewStudent = $view->element('student_chat_class_chivox', $viewData);
8110                $viewData['question_name'] = !empty($questionName) ? $questionName : $textbookName['textbook_name_eng'];
8111                $resultViewTeacher = $view->element('teacher_chat_class_chivox', $viewData);
8112                $response['resultViewStudent'] = $resultViewStudent;
8113                $response['resultViewTeacher'] = $resultViewTeacher;
8114
8115            } else {
8116                if (isset($result['error']) && !empty($result['error'])) {
8117                    $errorName = myTools::removeNonLettersAndLowercase($result['error']);
8118                    //NJ-26204: check if cases, (3) and (4), of error code 41030
8119                    if (in_array($errorName, Configure::read('error_41030_triggers'))) {
8120                        $response['chivoxErrorId'] = $response['chivoxError'] = $result['errId'] = 41030;
8121                        $result['error_method'] = __METHOD__;
8122                        $result['error_url'] = @$_SERVER['REQUEST_URI'];
8123
8124                        // - initialize slack and send
8125                        App::uses('mySlack', 'Lib');
8126                        $mySlack = new mySlack();
8127
8128                        // - report chivox error to slack.
8129                        $mySlack->createChivoxErrorContent($result);
8130                        // - NJ-3658 updateTrainingStatus
8131                        $response['chivoxTrainingDataOff'] = $this->TrainingData->updateTrainingStatus();
8132
8133                        // turn on NC Content error modal
8134                        if(!$this->NCContentList->turnOnNCContentErrorModal()) {
8135                            $this->log('NJ-26204 Failed to turn ON error modal flg.', 'debug');
8136                        }
8137                    }
8138                } else {
8139                    $response['error'] = !empty($result['error']) ? $result['error'] : "Chivox Error!";
8140                    // report chivox error to slack.
8141                    $this->sendChivoxSlack($result);
8142                }
8143            }
8144
8145            // return chivox type for app
8146            $response['chivox_type'] = $chivox_type;
8147            $params = array(
8148                "pj_identifier" => $pj_identifier,
8149                "user_id" => $userID,
8150                "env" => $ENV,
8151                "device_type" => "PC"
8152            );
8153            $getChivoxIdentifier = myTools::getChivoxIdentifier($params);
8154
8155            // get query_date
8156            $currentDay = date('Y-m-d');
8157            $queryCountToSave = array(
8158                'user_id' => $userID,
8159                'query_date' => $currentDay,
8160                'chivox_type' => $chivoxCountType,
8161                'chivox_callan_pp_id' => null,
8162                'chivox_monthly_test_id' => null,
8163                'chivox_textbook_category' => $textbookCategory,
8164                'question_count' => 1,
8165                'chivox_identifier' => $getChivoxIdentifier
8166            );
8167            $this->UsersChivoxCountQuery->updateQuestionQueryCount($queryCountToSave);
8168            // Delete aws
8169            $del = $this->FileStorage->removeFile(array("url" => $resultUpload['FileStorage']['url']));
8170            if (!$del) {
8171                $this->log('[NC-8372 uploadRecordedFileChivox Fail deleting the audio from File storage, AWS. ' . json_encode($resultUpload['FileStorage']['url']), 'debug');
8172            }
8173        }
8174        return json_encode($response);
8175    }
8176
8177    /**
8178     * @api {post} /teacher/api/saveConnectionSpeed saveConnectionSpeed()
8179     * @apiName saveConnectionSpeed
8180     * @apiGroup API
8181     * @apiDescription save teacher connection speed every 10 seconds
8182     * @apiSampleRequest off
8183     * 
8184     * @apiBody {Array} connection_speed The connection speed.
8185     * 
8186     * @apiSuccess {Boolean} success The status of the request.
8187     * 
8188     * @apiErrorExample {json} Success-Response:
8189     *     {
8190     *       "success": true
8191     *     }
8192     *
8193     * @apiErrorExample {js} Used in: Elements
8194     * Location: "view/Elements/chat_area_js.php"
8195     */
8196    /**
8197     * save teacher connection speed every 10 seconds
8198     */
8199    public function saveConnectionSpeed() {
8200        $this->layout = $this->autoRender = false;
8201        $res = array('success' => false);
8202        if ($this->request->is('post')) {
8203            try {
8204                if (isset($this->request->data['connection_speed']) && is_array($this->request->data['connection_speed'])) {
8205                    $logValues = ClassRegistry::init('LessonConnectionSpeedLog')->composeDataToInsert($this->request->data['connection_speed']);
8206                    $model = ClassRegistry::init("LogModel");
8207                    $query = $model->query("INSERT INTO 
8208                                                        lesson_connection_speed_logs
8209                                                        (chat_hash, user_id, teacher_id, download_speed, upload_speed, created, modified, created_ip, modified_ip)
8210                                                    VALUES 
8211                                                        $logValues");
8212                    if ($query) {
8213                        $res = array('success' => true);
8214                    } else {
8215                        $this->log(__METHOD__ . ' save_to_lesson_connection_speed_logs_failed', 'debug');
8216                    }
8217                }
8218            } catch(Exception $csl) {
8219                $this->log('[LESSON CONNECTION SPEED LOG SAVE FAILED] Caught Exception: ' . $csl->getMessage() . ' : ' . json_encode($this->request->data['connection_speed']), 'debug');
8220                $res = array('success' => false);
8221            }
8222        }
8223
8224        return json_encode($res);
8225    }
8226
8227    /**
8228     * @api {get} /teacher/api/disconnectionCancellationMemCached disconnectionCancellationMemCached()
8229     * @apiName disconnectionCancellationMemCached
8230     * @apiGroup API
8231     * @apiDescription Check if teacher has a recent canceled lesson(reservation) OR remove lesson cancelation memcached.
8232     * @apiSampleRequest off
8233     * 
8234     * @apiBody {String} checker The type of action to perform (check or delete).
8235     * 
8236     * @apiSuccess {Boolean} has_lessonCancel The status of the request.
8237     * 
8238     * @apiErrorExample {json} Success-Response:
8239     *     {
8240     *       "has_lessonCancel": true
8241     *     }
8242     *
8243     * @apiErrorExample {json} Error-Response:
8244     *     {
8245     *       "has_lessonCancel": false
8246     *     }
8247     * 
8248      * @apiErrorExample {js} Used in: JS
8249     * Location: "view/Home/index.php"
8250     */
8251    /**
8252    * Check if teacher has a recent canceled lesson(reservation) OR remove lesson cancelation memcached
8253    * @return bool
8254    */
8255    public function disconnectionCancellationMemCached () {
8256        $this->layout = $this->autoRender = false;
8257        $checkerType = (isset($this->request->query['checker'])) ? $this->request->query['checker'] : null;
8258        $res = array('has_lessonCancel' => false);
8259        $memcached = new myMemcached();
8260        $memKey = 'memDisconnectionCancellation_' . $this->Auth->user('id');
8261        $hasLessonCancel = $memcached->get($memKey);
8262        if ($hasLessonCancel) {
8263            if($checkerType == 'check') {
8264                $res['has_lessonCancel'] = true;
8265            } else if($checkerType == 'delete') {
8266                $memcached->delete($memKey);
8267            }
8268        }
8269        return json_encode($res);    
8270    }
8271
8272    /**
8273     * @api {post} /teacher/api/uploadAttachedFileToAWS uploadAttachedFileToAWS()
8274     * @apiName uploadAttachedFileToAWS
8275     * @apiGroup API
8276     * @apiDescription upload file aws and get equivalnet s3 urls
8277     * @apiSampleRequest off
8278     * 
8279     * @apiBody {File} attached_files The file to upload.
8280     * @apiBody {String} fileUrl The URL of the file.
8281     * @apiBody {String} refText The reference text.
8282     * 
8283     * @apiSuccess {String} fileUrl The URL of the file.
8284     * 
8285     * @apiErrorExample {json} Success-Response:
8286     *     {
8287     *       "fileUrl": "https://s3.amazonaws.com/bucket/filename.ext"
8288     *     }
8289     *
8290     * @apiErrorExample {json} Error-Response:
8291     *     {
8292     *       "fileUrl": ""
8293     *     }
8294     * 
8295     * @apiErrorExample {js} Used in: Elements
8296     * Location: "view/Elements/chat_area_js.php"
8297     */
8298    /**
8299    * upload file aws and get equivalnet s3 urls
8300    * @return string $fileUrl
8301    */
8302   public function uploadAttachedFileToAWS() {
8303        $this->autoRender = false;
8304        $fileUrl = '';
8305        // - check if request has a valid file
8306        if (!isset($_FILES['attached_files']['size']) || !$_FILES['attached_files']['size']) {
8307            return $fileUrl;
8308        }
8309        $fileSelected = $_FILES['attached_files'];
8310        $containerFileTemp = ROOT . '/instructor/webroot/files';
8311        if (!is_dir($containerFileTemp)) {
8312            mkdir($containerFileTemp);
8313        }        
8314        $filename     = time() . uniqid() . '_' . basename($fileSelected['name']);
8315        $full_url     = $containerFileTemp .'/'. $filename;
8316        $success     = move_uploaded_file($fileSelected['tmp_name'], $full_url);
8317        if ($success) {
8318            $tempResult = ClassRegistry::init('FileStorage')->uploadFile(array(
8319                'uploader_id'     => $this->Auth->user('id'),
8320                'uploader_type' => 26,
8321                'source'         => $full_url,
8322                'key'             => $filename,
8323                'file'             => $fileSelected,
8324                'delete_image'     => true
8325            ));
8326            $fileUrl = $tempResult['FileStorage']['url'];
8327        }
8328        return $fileUrl;
8329    }
8330
8331    /**
8332     * @api {get} /teacher/api/downloadChatLogFiles downloadChatLogFiles()
8333     * @apiName downloadChatLogFiles
8334     * @apiGroup API
8335     * @apiDescription download file from chatlogs
8336     * @apiSampleRequest off
8337     * 
8338     * @apiBody {String} file The URL of the file to download.
8339     *
8340     * @apiErrorExample {js} Used in: Elements
8341     * Location: "view/Elements/chat_area_js.php"
8342     * Location: "view/Elements/lesson_message_js.php"
8343     * 
8344     * @apiErrorExample {js} Used in: WebRTC
8345      * Location: "webroot/js/webrtcv2/event.student.js"
8346     */
8347    /**
8348    * download file from chatlogs
8349    */
8350    public function downloadChatLogFiles() {
8351        $this->autoRender = false;
8352        if (isset($this->request->query['file'])) {
8353            $fileUrl     = $this->request->query['file'];
8354            $remoteFile = str_replace(basename($fileUrl), urlencode(basename($fileUrl)), $fileUrl);
8355            $fileUrl     = basename($this->request->query['file']);
8356            $fileName     = explode('_', $fileUrl);
8357            $fileName     = isset($fileName[1]) ? str_replace($fileName[0].'_', '', $fileUrl) : $fileUrl;
8358            $tempFileDir = ROOT . '/instructor/webroot/files/' . $fileUrl;
8359             
8360            // ~ get file content from s3
8361            $cURLConnection = curl_init();
8362            curl_setopt($cURLConnection, CURLOPT_URL, $remoteFile);
8363            curl_setopt($cURLConnection, CURLOPT_RETURNTRANSFER, true);
8364            curl_setopt($cURLConnection, CURLOPT_HTTPHEADER, array('Referer: '.myTools::getUrl().'/'));
8365            $fileContent = curl_exec($cURLConnection);
8366            curl_close($cURLConnection);
8367
8368            file_put_contents($tempFileDir, $fileContent);
8369            header('Content-Length: '. filesize($tempFileDir));
8370            header('Content-Type: application/octet-stream');
8371            header('Content-Disposition: attachment; filename='. $fileName);
8372            readfile($tempFileDir);
8373            
8374            // - delete audio file if it exist
8375            if (file_exists($tempFileDir)) {
8376                unlink($tempFileDir);
8377            }
8378        }
8379        exit;
8380    }
8381
8382    /**
8383     * @api {post} /teacher/api/uploadeCancelDeleteFile uploadeCancelDeleteFile()
8384     * @apiName uploadeCancelDeleteFile
8385     * @apiGroup API
8386     * @apiDescription delete file when canceled
8387     * @apiSampleRequest off
8388     * 
8389     * @apiBody {Array} fileUrl The URL of the file to delete.
8390     * 
8391     * @apiSuccess {Boolean} success The status of the request.
8392     * 
8393     * @apiErrorExample {json} Success-Response:
8394     *     {
8395     *       "success": true
8396     *     }
8397     *
8398     * @apiErrorExample {js} Used in: Elements
8399     * Location: "view/Elements/chat_area_js.php"
8400     */
8401    /**
8402    * delete file when canceled
8403    */
8404    public function uploadeCancelDeleteFile() {
8405        $this->autoRender = false;
8406        $file = array();
8407        if (isset($this->request->data['fileUrl']) && $this->request->data['fileUrl']) {
8408            foreach ($this->request->data['fileUrl'] as $fileKey => $fileUrl) {
8409                ClassRegistry::init('FileStorage')->removeFile(array('url' => 'https://' . $fileUrl));
8410            }
8411        }
8412        return true;
8413    }
8414
8415    /**
8416     * @api {get} /teacher/api/uploadRecordedFileMerge uploadRecordedFileMerge()
8417     * @apiName uploadRecordedFileMerge
8418     * @apiGroup API
8419     * @apiDescription Save audio to aws, use to request chivox ai and google api merge
8420     * @apiSampleRequest off
8421     * 
8422     * @apiBody {File} audio The audio file to upload.
8423     * @apiBody {String} refText The reference text.
8424     * @apiBody {String} chivox_pc The chivox pc.
8425     * 
8426     * @apiSuccess {Boolean} success The status of the request.
8427     * @apiSuccess {String} filename The name of the file.
8428     * @apiSuccess {String} audioFileURL The URL of the audio file.
8429     * @apiSuccess {String} convertedSpeech The converted text.
8430     * @apiSuccess {String} resultView The HTML content.
8431     * 
8432     * @apiErrorExample {json} Success-Response:
8433     *     {
8434     *       "success": true,
8435     *       "filename": "filename.wav",
8436     *       "audioFileURL": "url",
8437     *       "convertedSpeech": "Converted text",
8438     *       "resultView": "HTML content"
8439     *     }
8440     *
8441      * @apiErrorExample {js} Used in: JS
8442      * Location: "webroot/js/custom_sound-recorder.js"
8443     */
8444    /**
8445    * Save audio to aws, use to request chivox ai and google api merge
8446    * @return mixed - json $response
8447    */
8448    public function uploadRecordedFileMerge () {
8449        $this->autoRender = false;
8450        $convertedSpeech = null;
8451        $response['success'] = false;
8452
8453        // - check if request has a valid file
8454        if (
8455            (!isset($_FILES['audio_wav']['size']) || !$_FILES['audio_wav']['size'])
8456            || (!isset($_FILES['audio_mp3']['size']) || !$_FILES['audio_mp3']['size'])
8457            || empty($this->request->data['refText'])
8458        ) {
8459            $response['error_code'] = "missing_parameters " . json_encode($_FILES);
8460            $response['error_code'] = "missing_parameters " . json_encode($this->request->data['refText']);
8461            return json_encode($response);
8462        }
8463
8464        $qvalue_mp3 = $_FILES['audio_mp3'];
8465        $qvalue_wav = $_FILES['audio_wav'];
8466        $chivox_pc = isset($this->request->data['chivox_pc']) ? $this->request->data['chivox_pc'] : 0;
8467        $rawRefText = isset($this->request->data['refText']) ? $this->request->data['refText'] : "";
8468        $chivoxSampleRate = Configure::read('chivox.sampleRate');
8469        $sampleRate = isset($this->request->data['sampleRate']) && $this->request->data['sampleRate'] <= $chivoxSampleRate ? $this->request->data['sampleRate'] : $chivoxSampleRate;
8470        $chivox_type = isset($this->request->data['chivox_type']) ? $this->request->data['chivox_type'] : 1;
8471        $kernel = isset($this->request->data['kernel']) ? $this->request->data['kernel'] : null;
8472        $addParams = isset($this->request->data['addParams']) ? json_decode($this->request->data['addParams'], true) : null;
8473        $qIndex = isset($addParams['qIndex']) ? $addParams['qIndex'] : "";
8474        $sendSuccessLamp = isset($addParams['sendSuccessLamp']) ? $addParams['sendSuccessLamp'] : "";
8475        $sendResult = isset($addParams['sendResult']) ? $addParams['sendResult'] : "";
8476        $reftextKeyword = isset($addParams['refTextAnswerKeyword']) ? $addParams['refTextAnswerKeyword'] : "";
8477        $targetKey = isset($addParams['targetKey']) ? $addParams['targetKey'] : "";
8478        $userID = isset($this->request->data['userID']) ? $this->request->data['userID'] : 0;
8479        $textbookCategory = isset($this->request->data['textbookCategory']) ? $this->request->data['textbookCategory'] : null;
8480        $connectId = !empty($this->request->data['connect_id']) ?$this->request->data['connect_id'] : 0;
8481        $storedRecordingFlag = !empty($this->request->data['store_audio_record']) ? true : false;
8482        $storedRecordingDetails = array();
8483        $newReviewResultFlg = isset($this->request->data['newReviewResultFlg']) ? $this->request->data['newReviewResultFlg'] : 0;
8484        $questionName    = !empty($this->request->data['question_name']) ? $this->request->data['question_name'] : null;
8485
8486        if ($kernel == 'en.sent.score' || $kernel == 'en.pred.score') {
8487            $rawRefText = json_decode($rawRefText,true);
8488            $refText = $rawRefText[0];
8489        } else {
8490            $refText = $rawRefText;
8491        }
8492
8493        // - Get audio temporary folder
8494        $audioContainer = ROOT . '/user/webroot/files';
8495        if (!is_dir($audioContainer)) {
8496            mkdir($audioContainer);
8497        }
8498
8499        //upload to amazon server
8500        $filename_mp3 = uniqid() . "_" . $_FILES['audio_mp3']['name'] . '.mp3';
8501        $filename_wav = uniqid() . "_" . $_FILES['audio_wav']['name'] . '.wav';
8502        //temporary location
8503        $tempLocation_mp3 = ROOT . '/user/webroot/files/'. $filename_mp3;
8504        $tempLocation_wav = ROOT . '/user/webroot/files/'. $filename_wav;
8505
8506        // - delete audio file if already exist
8507        if (file_exists($tempLocation_mp3)) {
8508            unlink($tempLocation_mp3);
8509        }
8510        if (file_exists($tempLocation_wav)) {
8511            unlink($tempLocation_wav);
8512        }
8513        move_uploaded_file(
8514            $qvalue_mp3['tmp_name'],
8515            $tempLocation_mp3
8516        );
8517        move_uploaded_file(
8518            $qvalue_wav['tmp_name'],
8519            $tempLocation_wav
8520        );
8521        
8522        // note this file is only temporary, so uploader_is is set to user_id only
8523        $uploader_id = $this->Auth->user('id');
8524
8525        // NC-9388: START CHIVOX PR0CESS
8526        $this->loadModel('FileStorage');
8527
8528        //-- append chatbox audio recoring details
8529        if ( !empty($storedRecordingFlag) ) {
8530            $chatboxAudRecordUpload = $this->FileStorage->uploadFile(array(
8531                'uploader_id' => $uploader_id,
8532                'uploader_type' => 34,
8533                'source' => $tempLocation_mp3,
8534                'key' => $filename_mp3,
8535                'file' => $_FILES['audio_mp3'],
8536                'delete_image' => false
8537            ));
8538        
8539            $fileUrl = $chatboxAudRecordUpload['FileStorage']['url'];
8540            $storedRecordingDetails['audio_url'] = str_replace('https://', '', $fileUrl);
8541            $storedRecordingDetails['textbook_name'] = '';
8542            //-- user current language
8543            $userLanguage = $this->User->fetchUserCurrentLanguage(array(
8544                'user_id' => $userID
8545            ));    
8546            $textbookName = $this->TextbookConnect->getTextbookName(array(
8547                'connect_id' => $connectId,
8548                'native_language' => $userLanguage,
8549                'translate_other_lang' => true
8550            ));                    
8551            $storedRecordingDetails['textbook_name'] .= !empty($textbookName['textbook_category_name']) ? $textbookName['textbook_category_name'] : '';
8552            $storedRecordingDetails['textbook_name'] .= !empty($textbookName['textbook_subcategory_name']) ? ' ' . $textbookName['textbook_subcategory_name'] : '';
8553            $storedRecordingDetails['textbook_name'] .= !empty($textbookName['textbook_name']) ? ' : ' . $textbookName['textbook_name'] : '';
8554        }
8555
8556        // - upload chivox
8557        $resultUpload = $this->FileStorage->uploadFile(array(
8558            'uploader_id' => $uploader_id,
8559            'uploader_type' => 19, // 19 - Chivox file
8560            'source' => $tempLocation_wav,
8561            'key' => $filename_wav,
8562            'file' => $_FILES['audio_wav'],
8563            'delete_image' => true
8564        ));
8565        
8566        // NC-9388: START GOOGLE API SPEECH TO TEXT PR0CESS
8567        if (isset($resultUpload['FileStorage']['url'])) {
8568            App::uses('myRedis', 'Lib');
8569            
8570            // - get speech
8571            $responseGoogle = myTools::performGooglSpeechToText([
8572                'audio_url' => $resultUpload['FileStorage']['url'],
8573                'user_language' => $userLanguage
8574            ]);
8575            
8576            // - check if success
8577            if ($responseGoogle['success']) {
8578                $response['success'] = true;
8579                $convertedSpeech = $responseGoogle['convertedSpeech'];
8580
8581                if (!empty($responseGoogle['totalTime']) && is_array($responseGoogle['totalTime'])) {
8582                    //get word timestamps
8583                    $sum = 0;
8584                    foreach ($responseGoogle['totalTime'] as $key => $value) {
8585                        foreach ($value as $key2 => $value2) {
8586                            //get each word start and end time
8587                            $startTime = floatval($value2['startTime']);
8588                            $endTime = floatval($value2['endTime']);
8589                            // sum the difference of start and end time
8590                            $sum += (float)($endTime -  $startTime);
8591                        }
8592                    }
8593
8594                    //save speech to text logs
8595                    $logParams = array(
8596                        'service' => 2,
8597                        'type' => 3,
8598                        'controller' => static::class,
8599                        'method' => __METHOD__,
8600                        'url' => $this->request->here(),
8601                        'value'    => number_format($sum, 2, '.', '')
8602                    );
8603                    $googleLogs = ClassRegistry::init('GoogleTranslateLog');
8604                    $result = $googleLogs->saveLog($logParams);
8605                    //check if logs saved
8606                    if (!$result) {
8607                        $this->log('error', __METHOD__ . " -- [Google Translate] Unable to save logs. ");
8608                    }
8609                }
8610            } else {
8611                $response['error_code'] = "unable to save to google " . json_encode($responseGoogle);
8612            }
8613        }
8614        // NC-9388: END GOOGLE API SPEECH TO TEXT PR0CESS
8615        
8616        // if aws audio is sucess request chivox thru java server
8617        if (isset($resultUpload['FileStorage']['url'])) {
8618
8619            // get chivox type: eiken for this textbook
8620            $chivoxCountType = 5;
8621
8622            // environment
8623            $ENV = Configure::read('ENVIRONMENT');
8624            $uri = myTools::getChivoxURI($ENV);
8625            $uri = isset($uri['chivox_utility']) ? $uri['chivox_utility'] : "";
8626
8627            $pjIdentifierParams = array(
8628                "pj_identifier" => Configure::read('chivox.identifiers.textbook_eiken'),
8629                "user_id" => $userID,
8630                "env" => $ENV,
8631                "device_type" => "PC"
8632            );
8633            $pj_identifier = myTools::getChivoxIdentifier($pjIdentifierParams);
8634
8635            if ($kernel == 'en.sent.score' || $kernel == 'en.pred.score') {
8636                $fields = array(
8637                    'action' => "audio",
8638                    'env' => $ENV,
8639                    'filename' => $filename_wav,
8640                    'refText' => $refText,
8641                    'userRecordedAudioUri' => $resultUpload['FileStorage']['url'],
8642                    'sampleRate' => $sampleRate,
8643                    'user_id' => $userID,
8644                    'pj_identifier' => $pj_identifier
8645                );
8646
8647                if ($kernel == 'en.pred.score') {
8648                    $fields['action'] = 'audioParagraph';
8649                }
8650
8651            } else {
8652                $fields = array(
8653                    'action' => "audioInterview",
8654                    'env' => $ENV,
8655                    'filename' => $filename_wav,
8656                    'refText' => $refText,
8657                    'userRecordedAudioUri' => $resultUpload['FileStorage']['url'],
8658                    'sampleRate' => $sampleRate,
8659                    'keywords' => $reftextKeyword,
8660                    'kernel' => $kernel,
8661                    'user_id' => $userID,
8662                    'pj_identifier' => $pj_identifier
8663                );
8664            }
8665
8666            //NJ-36647: add device type
8667            $fields['device_type'] = "PC";
8668
8669            $fields_string = "";
8670
8671            //url-ify the data for the POST
8672            foreach($fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; }
8673            rtrim($fields_string, '&');
8674
8675            $ch = curl_init($uri);
8676            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
8677            curl_setopt($ch, CURLOPT_HEADER, false);
8678            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
8679            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
8680            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 300); // set to 5 minutes
8681            curl_setopt($ch, CURLOPT_TIMEOUT, 300); // set to 5 minutes
8682            curl_setopt($ch, CURLOPT_POST, count($fields));
8683            curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string);
8684
8685            $result = curl_exec($ch);
8686            curl_close($ch);
8687            $result = json_decode($result, true);
8688
8689            if (isset($result['success']) && $result['success']) {
8690
8691                $response = $result;
8692                $response['raw_average_score'] = $result['average_score'];
8693                $getEquivalent = $this->getTextbookEquivalentScores($uploader_id, $targetKey);
8694                $response['average_score'] = isset($getEquivalent[$result['average_score']]) ? $getEquivalent[$result['average_score']] : $result['average_score'];
8695
8696                $viewFile = '';
8697                $viewData = array();
8698
8699                //NJ-7148
8700                if ($newReviewResultFlg) {
8701                    $viewData = array(
8702                        'details' => $result['details'],
8703                        'scoreValue' => $result['scoreValue'],
8704                        'average_score' => $response['average_score'],
8705                        'convertedSpeech' => $convertedSpeech,
8706                        'target_key' => $targetKey,
8707                        'chivox_type' => $chivox_type,
8708                        'review_flg' => 1,
8709                        'audio_url' => $fileUrl
8710                    );
8711                    $viewFile = 'eiken_mock_interview_review_result';
8712                } else {
8713                    if ($kernel == 'en.sent.score' || $kernel == 'en.pred.score') {
8714                        $viewData = array(
8715                            'details' => $result['details'],
8716                            'scoreValue' => $result['scoreValue'],
8717                            'refText' => $refText,
8718                            'audio_url' => $fileUrl,
8719                            'average_score' => $response['average_score'],
8720                            'chivox_type' => $chivox_type,
8721                            'target_key' => $targetKey
8722                        );
8723                        $viewFile = 'reading_ai_onlesson_result';
8724                    } else {
8725                        $viewData = array(
8726                            'average_score' => $response['average_score'],
8727                            'target_key' => $targetKey,
8728                            'audio_url' => $fileUrl,
8729                            'convertedSpeech' => $convertedSpeech,
8730                            'chivox_type' => 0 // - force set to 0 for display purposes
8731                        );
8732
8733                        $response['resultView'] = $convertedSpeech;
8734                    }
8735                }
8736
8737                if (!empty($viewData)) {
8738                    // instantiate view
8739                    $view = new View($this, false);
8740                    $view->layout = false;
8741
8742                    // - old design
8743                    if (!empty($viewFile)) {
8744                        $response['resultViewOld'] = $view->element($viewFile, $viewData);
8745                    }
8746
8747                    $resultViewStudent = $view->element('student_chat_class_chivox', $viewData);
8748                    $resultViewTeacher = $view->element('teacher_chat_class_chivox', $viewData);
8749                    
8750                    $response['resultViewStudent'] = $resultViewStudent;
8751                    $response['resultViewTeacher'] = $resultViewTeacher;
8752                }    
8753                
8754
8755                $this->log("file merge rsponse: " . json_encode($response));
8756            } else {
8757                $response['error'] = !empty($result['error']) ? $result['error'] : "Chivox Error!";
8758                // report chivox error to slack.
8759                $this->sendChivoxSlack($result);
8760            }
8761
8762            // return chivox type for app
8763            $params = array(
8764                "pj_identifier" => $pj_identifier,
8765                "user_id" => $userID,
8766                "env" => $ENV
8767            );
8768            $getChivoxIdentifier = myTools::getChivoxIdentifier($params);
8769
8770            // get query_date
8771            $currentDay = date('Y-m-d');
8772            $queryCountToSave = array(
8773                'user_id' => $userID,
8774                'query_date' => $currentDay,
8775                'chivox_type' => $chivoxCountType,
8776                'chivox_callan_pp_id' => null,
8777                'chivox_monthly_test_id' => null,
8778                'chivox_textbook_category' => $textbookCategory,
8779                'question_count' => 1,
8780                'chivox_identifier' => $getChivoxIdentifier
8781            );
8782            $this->UsersChivoxCountQuery->updateQuestionQueryCount($queryCountToSave);
8783
8784            // Delete aws
8785            $del = $this->FileStorage->removeFile(array("url" => $resultUpload['FileStorage']['url']));
8786            if (!$del) {
8787                $this->log('[NC-8372 uploadRecordedFileChivox Fail deleting the audio from File storage, AWS. ' . json_encode($resultUpload['FileStorage']['url']), 'debug');
8788            }
8789        }
8790        // NC-9388: END CHIVOX PR0CESS
8791        
8792        // - set converted speech
8793        $response['convertedSpeech'] = $convertedSpeech;
8794        if ( !empty($storedRecordingDetails) ) {
8795            $response = array_merge($response, $storedRecordingDetails);
8796        } else {
8797            $response['error_code'] = "unable to store audio file " . json_encode($storedRecordingDetails);
8798        }
8799        
8800        // - return
8801        return json_encode($response);
8802    }
8803
8804    /**
8805     * @api {post} /teacher/api/uploadRecordedFileMergeAsync uploadRecordedFileMergeAsync()
8806     * @apiName uploadRecordedFileMergeAsync
8807     * @apiGroup API
8808     * @apiDescription Save audio to aws, use to request chivox ai and google api merge
8809     * @apiSampleRequest off
8810     * 
8811     * @apiBody {File} audio The audio file to upload.
8812     * @apiBody {String} refText The reference text.
8813     * @apiBody {String} userID The user ID.
8814     * @apiBody {String} connect_id The connect ID.
8815     * @apiBody {String} convertedSpeech The converted speech.
8816     * @apiBody {String} store_audio_record The store audio record.
8817     * 
8818     * @apiSuccess {Boolean} success The status of the request.
8819     * @apiSuccess {String} filename The name of the file.
8820     * @apiSuccess {String} audioFileURL The URL of the audio file.
8821     * @apiSuccess {String} convertedSpeech The converted text.
8822     * @apiSuccess {String} resultView The HTML content.
8823     * 
8824     * @apiErrorExample {json} Success-Response:
8825     *     {
8826     *       "success": true,
8827     *       "filename": "filename.wav",
8828     *       "audioFileURL": "url",
8829     *       "convertedSpeech": "Converted text",
8830     *       "resultView": "HTML content"
8831     *     }
8832     *
8833     * @apiErrorExample {json} Error-Response:
8834     *     {
8835     *       "success": false,
8836     *       "error": "Chivox Error!"
8837     *     }
8838     */
8839    /**
8840     * Save audio to aws, use to request chivox ai and google api merge
8841     * @return mixed - json $response
8842     */
8843    public function uploadRecordedFileMergeAsync()
8844    {
8845        $this->autoRender = false;
8846        $response['success'] = false;
8847        $convertedSpeech = isset($this->request->data['convertedSpeech']) ? $this->request->data['convertedSpeech'] : '';
8848        $userID = isset($this->request->data['userID']) ? $this->request->data['userID'] : 0;
8849        $storedRecordingDetails = array();
8850        $storedRecordingFlag = !empty($this->request->data['store_audio_record']) ? true : false;
8851
8852        // note this file is only temporary, so uploader_is is set to user_id only
8853        $uploader_id = $this->Auth->user('id');
8854
8855        if (empty($convertedSpeech)) {
8856            $connectId = !empty($this->request->data['connect_id']) ? $this->request->data['connect_id'] : 0;
8857
8858            // - check if request has a valid file
8859            if (
8860                (!isset($_FILES['audio_wav']['size']) || !$_FILES['audio_wav']['size']) 
8861                || (!isset($_FILES['audio_mp3']['size']) || !$_FILES['audio_mp3']['size']) 
8862                || empty($this->request->data['refText'])
8863            ) {
8864                return json_encode($response);
8865            }
8866            $qvalue_mp3 = $_FILES['audio_mp3'];
8867            $qvalue_wav = $_FILES['audio_wav'];
8868
8869            // - Get audio temporary folder
8870            $audioContainer = ROOT . '/user/webroot/files';
8871            if (!is_dir($audioContainer)) {
8872                mkdir($audioContainer);
8873            }
8874
8875            //upload to amazon server
8876            $filename_mp3 = uniqid() . "_" . $_FILES['audio_mp3']['name'] . '.mp3';
8877            $filename_wav = uniqid() . "_" . $_FILES['audio_wav']['name'] . '.wav';
8878            //temporary location
8879            $tempLocation_mp3 = ROOT . '/user/webroot/files/'. $filename_mp3;
8880            $tempLocation_wav = ROOT . '/user/webroot/files/'. $filename_wav;
8881
8882            // - delete audio file if already exist
8883            if (file_exists($tempLocation_mp3)) {
8884                unlink($tempLocation_mp3);
8885            }
8886            if (file_exists($tempLocation_wav)) {
8887                unlink($tempLocation_wav);
8888            }
8889
8890            move_uploaded_file(
8891                $qvalue_mp3['tmp_name'],
8892                $tempLocation_mp3
8893            );
8894            move_uploaded_file(
8895                $qvalue_wav['tmp_name'],
8896                $tempLocation_wav
8897            );
8898
8899            // NC-9388: START CHIVOX PR0CESS
8900            $this->loadModel('FileStorage');
8901
8902            //-- append chatbox audio recoring details
8903            if (!empty($storedRecordingFlag)) {
8904                //-- upload chatbox audio recording
8905                $chatboxAudRecordUpload = $this->FileStorage->uploadFile(array(
8906                    'uploader_id' => $uploader_id,
8907                    'uploader_type' => 34,
8908                    'source' => $tempLocation_mp3,
8909                    'key' => $filename_mp3,
8910                    'file' => $_FILES['audio_mp3'],
8911                    'delete_image' => false
8912                ));
8913                
8914                //-- upload chatbox audio recording
8915                $fileUrl = $chatboxAudRecordUpload['FileStorage']['url'];
8916                $storedRecordingDetails['audio_url'] = str_replace('https://', '', $fileUrl);
8917                $storedRecordingDetails['textbook_name'] = '';
8918                //-- user current language
8919                $userLanguage = $this->User->fetchUserCurrentLanguage(array(
8920                    'user_id' => $userID
8921                ));
8922                $textbookName = $this->TextbookConnect->getTextbookName(array(
8923                    'connect_id' => $connectId,
8924                    'native_language' => $userLanguage,
8925                    'translate_other_lang' => true
8926                ));
8927                $storedRecordingDetails['textbook_name'] .= !empty($textbookName['textbook_category_name']) ? $textbookName['textbook_category_name'] : '';
8928                $storedRecordingDetails['textbook_name'] .= !empty($textbookName['textbook_subcategory_name']) ? ' ' . $textbookName['textbook_subcategory_name'] : '';
8929                $storedRecordingDetails['textbook_name'] .= !empty($textbookName['textbook_name']) ? ' : ' . $textbookName['textbook_name'] : '';
8930            }
8931
8932            // - upload file
8933            $resultUpload = $this->FileStorage->uploadFile(array(
8934                'uploader_id' => $uploader_id,
8935                'uploader_type' => 19, // 19 - Chivox file
8936                'source' => $tempLocation_wav,
8937                'key' => $filename_wav,
8938                'file' => $_FILES['audio_wav'],
8939                'delete_image' => true
8940            ));
8941            $responseGoogle = myTools::performGooglSpeechToText([
8942                'audio_url' => $resultUpload['FileStorage']['url'],
8943                'user_language' => $userLanguage
8944            ]);
8945
8946            // if google speech to text failed, prematurely end
8947            if (!$responseGoogle['success']) {
8948                $this->log('error', __METHOD__ . " -- [NC-9388][Google Translate] Unable convert to text. ");
8949                if (!empty($storedRecordingDetails)) {
8950                    $response = array_merge($response, $storedRecordingDetails);
8951                }
8952                return json_encode($response);
8953            }
8954
8955            $response['filename'] = $filename_wav;
8956            $response['audioFileURL'] = isset($resultUpload['FileStorage']['url']) && $resultUpload['FileStorage']['url'] ? $resultUpload['FileStorage']['url'] : '';
8957            $response['audioFileURLMp3'] = isset($chatboxAudRecordUpload['FileStorage']['url']) && $chatboxAudRecordUpload['FileStorage']['url'] ? $chatboxAudRecordUpload['FileStorage']['url'] : '';
8958            $response['convertedSpeech'] = $responseGoogle['convertedSpeech'] ?? "";
8959            $response['success'] = true;
8960
8961            if (!empty($storedRecordingDetails)) {
8962                $response = array_merge($response, $storedRecordingDetails);
8963            }
8964            return json_encode($response);
8965
8966        } else {
8967            $response['SendChivoxRequest'] = true;
8968            $chivox_pc = isset($this->request->data['chivox_pc']) ? $this->request->data['chivox_pc'] : 0;
8969            $rawRefText = isset($this->request->data['refText']) ? $this->request->data['refText'] : "";
8970            $chivoxSampleRate = Configure::read('chivox.sampleRate');
8971            $sampleRate = isset($this->request->data['sampleRate']) && $this->request->data['sampleRate'] <= $chivoxSampleRate ? $this->request->data['sampleRate'] : $chivoxSampleRate;
8972            $kernel = isset($this->request->data['kernel']) ? $this->request->data['kernel'] : null;
8973            $addParams = isset($this->request->data['addParams']) ? json_decode($this->request->data['addParams'], true) : null;
8974            $reftextKeyword = isset($addParams['refTextAnswerKeyword']) ? $addParams['refTextAnswerKeyword'] : "";
8975            $targetKey = isset($addParams['targetKey']) ? $addParams['targetKey'] : "";
8976            $textbookCategory = isset($this->request->data['textbookCategory']) ? $this->request->data['textbookCategory'] : null;
8977            $newReviewResultFlg = isset($this->request->data['newReviewResultFlg']) ? $this->request->data['newReviewResultFlg'] : 0;
8978            $filename = isset($this->request->data['filename']) && $this->request->data['filename'] ? $this->request->data['filename'] : '';
8979            $audioFileURL = isset($this->request->data['audioFileURL']) && $this->request->data['audioFileURL'] ? $this->request->data['audioFileURL'] : '';
8980            $chivox_type = isset($this->request->data['chivox_type']) ? $this->request->data['chivox_type'] : 1;
8981            $questionName = !empty($this->request->data['question_name']) ? $this->request->data['question_name'] : null;
8982            $audioFileURLMp3 = !empty($this->request->data['audioFileURLMp3']) ? $this->request->data['audioFileURLMp3'] : null;
8983
8984            if ($kernel == 'en.sent.score' || $kernel == 'en.pred.score') {
8985                $rawRefText = json_decode($rawRefText, true);
8986                $refText = $rawRefText[0];
8987            } else {
8988                $refText = $rawRefText;
8989            }
8990            // if aws audio is sucess request chivox thru java server
8991            if ($audioFileURL) {
8992
8993                // get chivox type: eiken for this textbook
8994                $chivoxCountType = 5;
8995
8996                // environment
8997                $ENV = Configure::read('ENVIRONMENT');
8998                $uri = myTools::getChivoxURI($ENV);
8999                $uri = isset($uri['chivox_utility']) ? $uri['chivox_utility'] : "";
9000
9001                $pjIdentifierParams = array(
9002                    "pj_identifier" => Configure::read('chivox.identifiers.textbook_eiken'),
9003                    "user_id" => $userID,
9004                    "env" => $ENV,
9005                    "device_type" => "PC"
9006                );
9007                $pj_identifier = myTools::getChivoxIdentifier($pjIdentifierParams);
9008
9009                if ($kernel == 'en.sent.score' || $kernel == 'en.pred.score') {
9010                    $fields = array(
9011                        'action' => "audio",
9012                        'env' => $ENV,
9013                        'filename' => $filename,
9014                        'refText' => $refText,
9015                        'userRecordedAudioUri' => $audioFileURL,
9016                        'sampleRate' => $sampleRate,
9017                        'user_id' => $userID,
9018                        'pj_identifier' => $pj_identifier
9019                    );
9020
9021                    if ($kernel == 'en.pred.score') {
9022                        $fields['action'] = 'audioParagraph';
9023                    }
9024
9025                } else {
9026                    $fields = array(
9027                        'action' => "audioInterview",
9028                        'env' => $ENV,
9029                        'filename' => $filename,
9030                        'refText' => $refText,
9031                        'userRecordedAudioUri' => $audioFileURL,
9032                        'sampleRate' => $sampleRate,
9033                        'keywords' => $reftextKeyword,
9034                        'kernel' => $kernel,
9035                        'user_id' => $userID,
9036                        'pj_identifier' => $pj_identifier
9037                    );
9038                }
9039
9040                $fields_string = "";
9041
9042                //NJ-36647: add device type
9043                $fields['device_type'] = "PC";
9044
9045                //url-ify the data for the POST
9046                foreach ($fields as $key => $value) {
9047                    $fields_string .= $key . '=' . $value . '&';
9048                }
9049                rtrim($fields_string, '&');
9050
9051                $this->log(__METHOD__ . ' [Initializing CURL] ' . json_encode($fields), 'debug');
9052
9053                $ch = curl_init($uri);
9054                curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
9055                curl_setopt($ch, CURLOPT_HEADER, false);
9056                curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
9057                curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
9058                curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 300); // set to 5 minutes
9059                curl_setopt($ch, CURLOPT_TIMEOUT, 300); // set to 5 minutes
9060                curl_setopt($ch, CURLOPT_POST, count($fields));
9061                curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string);
9062
9063                $result = curl_exec($ch);
9064                curl_close($ch);
9065                $result = json_decode($result, true);
9066
9067                if (isset($result['success']) && $result['success']) {
9068
9069                    $response = $result;
9070                    $response['raw_average_score'] = $result['average_score'];
9071                    $getEquivalent = $this->getTextbookEquivalentScores($uploader_id, $targetKey);
9072                    $response['average_score'] = isset($getEquivalent[$result['average_score']]) ? $getEquivalent[$result['average_score']] : $result['average_score'];
9073                    $response['convertedSpeech'] = $convertedSpeech;
9074                    
9075                    $viewFile = '';
9076                    $viewData = array();
9077                    //NJ-7148
9078                    if ($newReviewResultFlg) {
9079                        $viewData = array(
9080                            'details' => $result['details'],
9081                            'scoreValue' => $result['scoreValue'],
9082                            'average_score' => $response['average_score'],
9083                            'convertedSpeech' => $convertedSpeech,
9084                            'audio_url' => $audioFileURLMp3,
9085                            'chivox_pc' => $chivox_pc,
9086                            'target_key' => $targetKey,
9087                            'chivox_type' => $chivox_type,
9088                            'review_flg' => 1
9089                        );
9090                        $viewFile = 'eiken_mock_interview_review_result';
9091                    } else {
9092                        if ($kernel == 'en.sent.score' || $kernel == 'en.pred.score') {
9093                            $viewData = array(
9094                                'details' => $result['details'],
9095                                'scoreValue' => $result['scoreValue'],
9096                                'refText' => $refText,
9097                                'audio_url' => $audioFileURLMp3,
9098                                'average_score' => $response['average_score'],
9099                                'chivox_type' => $chivox_type,
9100                                'target_key' => $targetKey
9101                            );
9102                            $viewFile = 'reading_ai_onlesson_result';
9103                        } else {
9104                            $viewData = array(
9105                                'average_score' => $response['average_score'],
9106                                'target_key' => $targetKey,
9107                                'audio_url' => $audioFileURLMp3,
9108                                'convertedSpeech' => $convertedSpeech,
9109                                'chivox_type' => 0 // - force set to 0 for display purposes
9110                            );
9111
9112                            $response['resultView'] = $convertedSpeech;
9113                        }
9114                    }
9115
9116                    if (!empty($viewData)) {
9117                        // instantiate view
9118                        $view = new View($this, false);
9119                        $view->layout = false;
9120
9121                        // - old design
9122                        if (!empty($viewFile)) {
9123                            $response['resultViewOld'] = $view->element($viewFile, $viewData);
9124                        }
9125
9126                        $resultViewStudent = $view->element('student_chat_class_chivox', $viewData);
9127                        $resultViewTeacher = $view->element('teacher_chat_class_chivox', $viewData);
9128                        
9129                        $response['resultViewStudent'] = $resultViewStudent;
9130                        $response['resultViewTeacher'] = $resultViewTeacher;
9131                    }    
9132                    
9133                    $this->log("file merge async rsponse: " . json_encode($response));
9134                } else {
9135                    $response['error'] = !empty($result['error']) ? $result['error'] : "Chivox Error!";
9136                    // report chivox error to slack.
9137                    $this->sendChivoxSlack($result);
9138                }
9139
9140                // return chivox type for app
9141                $params = array(
9142                    "pj_identifier" => $pj_identifier,
9143                    "user_id" => $userID,
9144                    "env" => $ENV
9145                );
9146                $getChivoxIdentifier = myTools::getChivoxIdentifier($params);
9147
9148                // get query_date
9149                $currentDay = date('Y-m-d');
9150                $queryCountToSave = array(
9151                    'user_id' => $userID,
9152                    'query_date' => $currentDay,
9153                    'chivox_type' => $chivoxCountType,
9154                    'chivox_callan_pp_id' => null,
9155                    'chivox_monthly_test_id' => null,
9156                    'chivox_textbook_category' => $textbookCategory,
9157                    'question_count' => 1,
9158                    'chivox_identifier' => $getChivoxIdentifier
9159                );
9160                $this->UsersChivoxCountQuery->updateQuestionQueryCount($queryCountToSave);
9161
9162                $this->loadModel('FileStorage');
9163                // Delete aws
9164                $del = $this->FileStorage->removeFile(array("url" => $audioFileURL));
9165                if (!$del) {
9166                    $this->log('[NC-8372 uploadRecordedFileChivox Fail deleting the audio from File storage, AWS. ' . json_encode($audioFileURL), 'debug');
9167                }
9168            }
9169            // NC-9388: END CHIVOX PR0CESS
9170            return json_encode($response);
9171        }
9172    }
9173
9174    /**
9175     * @api {post} /teacher/api/getTimezoneUserAndTeacher getTimezoneUserAndTeacher()
9176     * @apiName getTimezoneUserAndTeacher
9177     * @apiGroup API
9178     * @apiDescription Get timezone
9179     * @apiSampleRequest off
9180     * 
9181     * @apiBody{String} chatHash The chat hash.
9182     * @apiBody{String} user_id The user ID.
9183     * 
9184     * @apiSuccess {Boolean} status The status of the request.
9185     * @apiSuccess {Array} data The data of the request. (`$this->Timezone->getTimezoneUserAndTeacher()`)
9186     * 
9187     * @apiErrorExample {json} Success-Response:
9188     *     {
9189     *       "status": true,
9190     *       "data": {...}
9191     *     }
9192     *
9193     * @apiErrorExample {js} Used in: Elements
9194     * Location: "view/Elements/chat_area_js.php"
9195     */
9196        /**
9197     * Get timezone
9198     * @return mixed - json_encoded array
9199     */
9200    public function getTimezoneUserAndTeacher() {
9201        $this->layout = '';
9202        $this->autoRender = false;
9203        $chatHash = $this->request['data']['chatHash'];
9204        $lessonOnAir = $this->LessonOnair->find('first',[
9205            'conditions' => [
9206                'LessonOnair.chat_hash' => $chatHash
9207            ],
9208            'recursive' => -1 
9209        ]);
9210        $teacherId = $this->Auth->user('id');
9211        $response = $this->Timezone->getTimezoneUserAndTeacher($teacherId, $lessonOnAir['LessonOnair']['user_id']);
9212        
9213        return json_encode([
9214            'status' => true,
9215            'data' => $response
9216        ]);
9217    }
9218
9219    /**
9220     * @api {post} /teacher/api/uploadRecordedFileAsync uploadRecordedFileAsync()
9221     * @apiName uploadRecordedFileAsync
9222     * @apiGroup API
9223     * @apiDescription Save audio and get equivalent text from Google API Asyncronous recognition
9224     * @apiSampleRequest off
9225     * 
9226     * @apiBody {File} audio The audio file to upload.
9227     * @apiBody {String} connect_id The connect ID.
9228     * @apiBody {String} store_audio_record The store audio record.
9229     * @apiBody {String} user_id The user ID.
9230     * 
9231     * @apiSuccess {Boolean} success The status of the request.
9232     * @apiSuccess {String} convertedSpeech The converted text.
9233     * @apiSuccess {Array} config The configuration.
9234     * @apiSuccess {String} audio_url The URL of the audio file.
9235     * @apiSuccess {String} textbook_name The textbook name.
9236     * 
9237     * @apiErrorExample {json} Success-Response:
9238     *     {
9239     *       "success": true,
9240     *       "convertedSpeech": "Converted text",
9241     *       "config": [],
9242     *       "audio_url": "url",
9243     *       "textbook_name": "Category Subcategory : Textbook"
9244     *     }
9245     *
9246     * @apiErrorExample {json} Error-Response:
9247     *     {
9248     *       "success": false,
9249     *       "convertedSpeech": null,
9250     *       "config": []
9251     *     }
9252     */
9253    /**
9254    * Save audio and get equivalent text from Google API Asyncronous recognition
9255    * @return mixed - json $response
9256    */
9257    public function uploadRecordedFileAsync() {
9258        $this->autoRender = false;
9259        $textbookNameHolder = $audioUrl = "";
9260        $response = array(
9261            'success' => false,
9262            'convertedSpeech' => '{"jpn" : "〔エラー〕:音声認識に問題があります。ヘッドセットあるいはイヤホンの着用/インターネット環境のご確認をお願いします","eng" : "Error : There is a problem with voice recognition. Please wear a headset or earphones and check your internet environment"}',
9263            'config' => array()
9264        );
9265
9266        $uploader_id = $this->Auth->user('id');
9267        if(empty($uploader_id) || !$uploader_id){
9268            return json_encode($response);
9269        }
9270        // check if request has a valid file
9271        if ((!isset($_FILES['audio_wav']['size']) || !$_FILES['audio_wav']['size']) || (!isset($_FILES['audio_mp3']['size']) || !$_FILES['audio_mp3']['size'])) {
9272            return json_encode($response);
9273        }
9274
9275        $connectId                 = !empty($this->request->data['connect_id']) ? $this->request->data['connect_id'] : null;
9276        $storedRecordingFlag     = !empty($this->request->data['store_audio_record']) ? true : false;
9277        $userId                    = !empty($this->request->data['user_id']) ? $this->request->data['user_id'] : null;
9278        $questionName            = !empty($this->request->data['question_name']) ? $this->request->data['question_name'] : null;
9279        $questionTitle            = !empty($this->request->data['question_title']) ? $this->request->data['question_title'] : null;
9280        $isSpeakTrainIelts        = !empty($this->request->data['isSpeakTrainIelts']) ? $this->request->data['isSpeakTrainIelts'] : false;
9281
9282        // - override question name
9283        if (!empty($questionTitle)) {
9284            $questionName = $questionTitle;
9285        }
9286        
9287        // Get audio temporary folder
9288        $audioContainer = ROOT . '/instructor/webroot/sound';
9289        if (!is_dir($audioContainer)) {
9290            mkdir($audioContainer);
9291        }
9292
9293        // move the file from temp name to local folder using $output name
9294        $wavInput = $_FILES['audio_wav']['tmp_name'];
9295        $mp3Input = $_FILES['audio_mp3']['tmp_name'];
9296
9297        $fileNameWAV = $uploader_id . $_FILES['audio']['name'] . '.wav';
9298        $fileNameMP3 = $uploader_id . $_FILES['audio']['name'] . '.mp3';
9299
9300        $wavOutput = $audioContainer . '/' . $fileNameWAV;
9301        $mp3Output = $audioContainer . '/' . $fileNameMP3;
9302
9303        // delete audio file if already exist
9304        if (file_exists($wavOutput)) {
9305            unlink($wavOutput);
9306        }
9307        if (file_exists($mp3Output)) {
9308            unlink($mp3Output);
9309        }
9310        // upload local file
9311        $moveFileStatusWav = move_uploaded_file($wavInput, $wavOutput);
9312        $moveFileStatusMp3 = move_uploaded_file($mp3Input, $mp3Output);
9313        //-- Move audio recordings to s3 
9314        $this->loadModel('FileStorage');
9315        if ($moveFileStatusWav && $moveFileStatusMp3 && !empty($storedRecordingFlag)) {
9316            $wavFilename = time() . uniqid() . '_wav_' . basename($wavOutput);
9317            $mp3Filename = time() . uniqid() . '_mp3_' . basename($mp3Output);
9318            $wavResult = $this->FileStorage->uploadFile([
9319                'uploader_id'  => $this->Auth->user('id'),
9320                'uploader_type' => 34,
9321                'source'       => $wavOutput,
9322                'key'          => $wavFilename,
9323                'file'         => $_FILES['audio_wav'],
9324                'delete_image' => true
9325            ]);
9326            
9327            $mp3Result = $this->FileStorage->uploadFile([
9328                'uploader_id'  => $this->Auth->user('id'),
9329                'uploader_type' => 34,
9330                'source'       => $mp3Output,
9331                'key'          => $mp3Filename,
9332                'file'         => $_FILES['audio_mp3'],
9333                'delete_image' => true
9334            ]);
9335            $fileUrl = $mp3Result['FileStorage']['url'];
9336            if (!empty($fileUrl)) {
9337                $response['audio_url'] = str_replace('https://', '', $fileUrl);
9338            }
9339        }
9340
9341        // query speech to text
9342        if ($response['audio_url'] && !$isSpeakTrainIelts) {
9343            
9344            // - debug lang ni. remove ni siya.
9345            $responseGoogle = myTools::performGooglSpeechToText([
9346                'audio_url' => $wavResult['FileStorage']['url'],
9347            ]);
9348        
9349            // - save log
9350            if (isset($responseGoogle['totalTime']) && !empty($responseGoogle['totalTime'])) {
9351            
9352                $response['convertedSpeech'] = $responseGoogle['convertedSpeech'] ?? '';
9353                $sum = str_replace('s', '', $responseGoogle['totalTime']);
9354
9355                //save speech to text logs
9356                $logParams = array(
9357                    'service' => 2,
9358                    'type' => 3,
9359                    'controller' => static::class,
9360                    'method' => __METHOD__,
9361                    'url' => $this->request->here(),
9362                    'value'    => number_format($sum, 2 , '.', '')
9363                );
9364                $googleLogs = ClassRegistry::init('GoogleTranslateLog');
9365                $result = $googleLogs->saveLog($logParams);        
9366                //check if logs saved
9367                if(!$result){
9368                    $this->log('error', __METHOD__." -- [Google Translate] Unable to save logs. ");
9369                }
9370            }
9371        }
9372
9373        if ($isSpeakTrainIelts) {
9374            $response['convertedSpeech'] = '';
9375        }
9376
9377        //-- user current language
9378        $userLanguage = $this->User->fetchUserCurrentLanguage(array(
9379            'user_id' => $userId
9380        ));
9381        $textbookName = $this->TextbookConnect->getTextbookName(array(
9382            'connect_id' => $connectId,
9383            'native_language' => $userLanguage,
9384            'translate_other_lang' => true
9385        ));
9386        $response['textbook_name'] .= !empty($textbookName['textbook_category_name']) ? $textbookName['textbook_category_name'] : '';
9387        $response['textbook_name'] .= !empty($textbookName['textbook_subcategory_name']) ? ' ' . $textbookName['textbook_subcategory_name'] : '';
9388        $response['textbook_name'] .= !empty($textbookName['textbook_name']) ? ' : ' . $textbookName['textbook_name'] : '';
9389
9390        // instantiate view
9391        $view = new View($this, false);
9392        $view->layout = false;
9393
9394        $viewData = array(
9395            'convertedSpeech' => $response['convertedSpeech'],
9396            'audio_url' => $fileUrl,
9397            'isSpeechRecording' => 1,
9398            'question_name' => !empty($questionName) ? $questionName : $textbookName['textbook_name']
9399        ); 
9400
9401        $resultViewStudent = $view->element('student_chat_class_chivox', $viewData);
9402        $viewData['question_name'] = !empty($questionName) ? $questionName : $textbookName['textbook_name_eng'];
9403        $resultViewTeacher = $view->element('teacher_chat_class_chivox', $viewData);
9404
9405        $response['resultViewStudent'] = $resultViewStudent;
9406        $response['resultViewTeacher'] = $resultViewTeacher;
9407
9408        $response['success'] = true;
9409
9410        // - delete audio file if already exist
9411        if (file_exists($output)) {
9412            unlink($output);
9413        }
9414        
9415        return json_encode($response);
9416    }
9417    
9418    /**
9419     * @api {get} /teacher/api/debugSocketLog debugSocketLog()
9420     * @apiName debugSocketLog
9421     * @apiGroup API
9422     * @apiDescription Debug socket log
9423     * @apiSampleRequest off
9424     * 
9425     * @apiBody {String} chat_hash The chat hash.
9426     * @apiBody {String} teacher_id The teacher ID.
9427     * @apiBody {String} user_id The user ID.
9428     * @apiBody {String} temp_id The temporary ID.
9429     * @apiBody {Object} data The debug data.
9430     * 
9431     * @apiSuccess {Boolean} success The status of the request.
9432     *
9433     * @apiErrorExample {json} Success-Response:
9434     *     {
9435     *       "success": true
9436     *     }
9437     *
9438     * @apiErrorExample {js} Used in: Elements
9439     * Location: "view/Elements/chat_area_js.php"
9440     * 
9441     * @apiErrorExample {js} Used in: View
9442     * Location: "view/Elements/HtmlTextBook.php"
9443     * 
9444      * @apiErrorExample {js} Used in: AngularJS
9445      * Location: "webroot/js/ng/app.js"
9446     * 
9447     * @apiErrorExample {js} Used in: WebRTC
9448      * Location: "webroot/js/webrtcv2/connect.js"
9449      * Location: "webroot/js/webrtcv2/connectEnvModal.js"
9450     */
9451    public function debugSocketLog($chat_hash = "", $teacher_id = "", $user_id = "", $temp_id = ""){
9452        $this->autoRender = false;
9453
9454        // - get data
9455        $data = json_decode($this->request->input(),true);
9456        
9457        // - set slack message
9458        $mySlack = new mySlack();
9459        $mySlack->token = "xoxb-392902820692-6499139810404-RHHGc6Z5ZB9o2KkiGhzTTtlQ";
9460        $mySlack->username = "Bad Teacher Connection Debug ";
9461        $mySlack->text = "";
9462        $mySlack->channel = myTools::checkChannel("#bad-teacher-connections", "#bad-teacher-connections-dev");
9463
9464        if($teacher_id && $user_id) {
9465            // Get track log number
9466            $lessonNumber = ClassRegistry::init('LessonTrackLog')->find('first', array(
9467                'fields' => 'LessonTrackLog.lesson_number',
9468                'conditions' => ['LessonTrackLog.chat_hash' => $chat_hash],
9469                'order' => ['LessonTrackLog.id' => 'DESC']
9470            ));
9471        
9472            if (empty($lessonNumber)) {
9473                // Create track log if not found
9474                $trackParam = [
9475                    'flag' => 'create',
9476                    'data' => [
9477                        'chat_hash' => $chat_hash,
9478                        'user_id' => $user_id,
9479                        'teacher_id' => $teacher_id
9480                    ]
9481                ];
9482        
9483                ClassRegistry::init('LessonTrackLog')->processLog($trackParam);
9484        
9485                // Get track log number after creation
9486                $lessonNumber = ClassRegistry::init('LessonTrackLog')->find('first', array(
9487                    'fields' => 'LessonTrackLog.lesson_number',
9488                    'conditions' => ['LessonTrackLog.chat_hash' => $chat_hash],
9489                    'order' => ['LessonTrackLog.id' => 'DESC']
9490                ));
9491            }
9492
9493            $userId = Configure::read('ENVIRONMENT') === "PRODUCTION" ? '<@U0410PF1WUT> <@U01S0E3Q9HQ> <@U05V885BMNK>' : '<@U0410PF1WUT> <@U01S0E3Q9HQ> <@U05V885BMNK>';
9494
9495            $mySlack->link_names = true;
9496            $mySlack->text = $userId . "\n";
9497            $mySlack->text .= "```";
9498            $mySlack->text .= "Error" . "\n";
9499            $mySlack->text .= $data["message"] . "\n";
9500            $mySlack->text .= "Date = " . date('Y-m-d H:i:s') . "\n";
9501            $mySlack->text .= "Lesson ID = " . json_encode($lessonNumber['LessonTrackLog']['lesson_number']) . "\n";
9502            $mySlack->text .= "Teacher ID = " . $teacher_id . "\n";
9503            $mySlack->text .= "Temp ID = " . $temp_id . "\n";
9504            $mySlack->text .= "Socket Log = " . $data["socket_log"] . "\n";
9505
9506        } else {
9507            $mySlack->text .= "```";
9508            $mySlack->text .= "chat_hash: " . $chat_hash . "\n";
9509            $mySlack->text .= "content: " . isset($data["message"]) ? $data["message"] : '-';
9510
9511            // - if has user_type
9512            if (isset($data['user_type']) && $data['user_type']) {
9513                $mySlack->text .= "\n";
9514                $mySlack->text .= "user: " . isset($data["user_type"]) ? $data["user_type"] : '-';
9515            }
9516            
9517        }
9518
9519        // - end slack message
9520        $mySlack->text .= "```";
9521        
9522        // - send slack message
9523        $test = $mySlack->postMessage();
9524        die();
9525    }
9526    //unused function, 
9527    public function logTeacherDebugError() {
9528        $this->autoRender = false;
9529        $response = array('status' => 'error', 'message' => 'An error occurred');
9530
9531        if ($this->request->is('ajax')) {
9532            $error = $this->request->input('json_decode', true)['error'] ?? 'Unknown error';
9533
9534            try {
9535                $this->sendSlackDebugMessage($error);
9536                $response = array('status' => 'success', 'message' => 'Error logged and reported');
9537            } catch (Exception $e) {
9538                $response['message'] = 'Failed to report error: ' . $e->getMessage();
9539            }
9540        }
9541
9542        return json_encode($response);
9543    }
9544
9545    private function sendSlackDebugMessage($error) {
9546        // - set slack message
9547        $mySlack = new mySlack();
9548        $mySlack->token = "xoxb-392902820692-6499139810404-RHHGc6Z5ZB9o2KkiGhzTTtlQ";
9549        $mySlack->username = "Teacher Debug Logger";
9550        $mySlack->channel = myTools::checkChannel("#nc-teacher-errors", "#fdc-test-channel-debug");
9551
9552        $referrerDebug = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : 'No referrer set';
9553        $urlDebug = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : 'No URI set';
9554
9555        $mySlack->text = "";
9556        $mySlack->text .= "```";
9557        $mySlack->text .= "Error: " . json_encode($error);
9558        $mySlack->text .= "\nTimestamp: " . date('Y-m-d H:i:s');
9559        $mySlack->text .= " \nreferrer: " . json_encode($referrerDebug);
9560        $mySlack->text .= " \nurl: " . json_encode($urlDebug);
9561        $mySlack->text .= "```";
9562        $test = $mySlack->postMessage();        
9563    }
9564
9565    /**
9566     * @api {post} /teacher/api/saveSkywayDebug saveSkywayDebug()
9567     * @apiName saveSkywayDebug
9568     * @apiGroup API
9569     * @apiDescription Save skyway debug
9570     * @apiSampleRequest off
9571     * 
9572     * @apiBody {String} chat_hash The chat hash.
9573     * @apiBody {String} message The debug message.
9574     * 
9575     * @apiSuccess {Boolean} success The status of the request.
9576     *
9577     * @apiErrorExample {json} Success-Response:
9578     *     {
9579     *       "success": true
9580     *     }
9581     *
9582     * @apiErrorExample {json} Error-Response:
9583     *     {
9584     *       "success": false
9585     *     }
9586     * 
9587     * @apiErrorExample {js} Used in: WebRTC
9588      * Location: "webroot/js/webrtcv2/event.common.js"
9589
9590     * @apiErrorExample {js} Used in: JS
9591      * Location: "webroot/js/socket.debug.js"
9592     */
9593    public function saveSkywayDebug($chat_hash = ""){
9594        // - if no chathash
9595        if (!$chat_hash) {
9596            return false;
9597        }
9598        
9599        // - data
9600        $data = json_decode($this->request->input(),true);
9601
9602        // - save login history
9603        $this->TeachersDebugSkyway->clear();
9604        $this->TeachersDebugSkyway->create();
9605        $this->TeachersDebugSkyway->set(array(
9606            'chat_hash' => $chat_hash,
9607            'message' => $data["message"],
9608        ));
9609        $this->TeachersDebugSkyway->save();
9610        die();
9611    }
9612
9613    /**
9614     * @api {post} /teacher/api/reCheckReservedLesson reCheckReservedLesson()
9615     * @apiName reCheckReservedLesson
9616     * @apiGroup API
9617     * @apiDescription checking reserved lesson before changing teacher status & logout
9618     * @apiSampleRequest off
9619     * 
9620     * @apiSuccess {Boolean} is_reserved The status of the request.
9621     * 
9622     * @apiErrorExample {json} Success-Response:
9623     *     {
9624     *       "is_reserved": true
9625     *     }
9626     *
9627      * @apiErrorExample {js} Used in: AngularJS
9628      * Location: "webroot/js/ng/controller/header.js"
9629     */
9630    /**
9631     * checking reserved lesson before changing teacher status & logout
9632     * @param mixed - teacherId
9633     */
9634    public function reCheckReservedLesson() {
9635        $this->autoRender = false;
9636        $isReserved = false;
9637        $teacherId = $this->Auth->user('id');
9638
9639        if ($teacherId && $this->LessonSchedule->checkSubTeacherLessonNow($teacherId)) {
9640            $isReserved = true;
9641        }
9642
9643        return json_encode(array('is_reserved' => $isReserved));
9644    }
9645
9646    /**
9647     * @api {post} /teacher/api/virtualCameraAllow virtualCameraAllow()
9648     * @apiName virtualCameraAllow
9649     * @apiGroup API
9650     * @apiDescription Get virtual camera allow
9651     * @apiSampleRequest off
9652     * 
9653     * @apiSuccess {Array} result The result of the request.
9654     * 
9655     * @apiErrorExample {json} Success-Response:
9656     *     {
9657     *       "0": "Character1",
9658     *       "1": "Character2",
9659     *       ...
9660     *     }
9661     *
9662     * @apiErrorExample {js} Used in: Elements
9663     * Location: "view/Elements/header.php"
9664     * 
9665     * @apiErrorExample {js} Used in: View
9666     * Location: "view/Elements/index.php"
9667     */
9668    public function virtualCameraAllow() {
9669        $this->autoRender = false;
9670        $result = array(); // set default as empty
9671
9672        $this->CharacterSettings->openDBReplica();
9673        $characterSettings = $this->CharacterSettings->find('all', array(
9674            'fields' => array(
9675                'CharacterSettings.name'
9676            ),
9677            'recursive' => -1
9678        ));
9679        $this->CharacterSettings->closeDBReplica();
9680        if (isset($characterSettings) && is_array($characterSettings)) {
9681            foreach($characterSettings as $key => $value){
9682                $result[$key] = $value['CharacterSettings']['name'];
9683            }
9684        }
9685
9686        return json_encode($result);
9687    }
9688
9689    private function getTextbookEquivalentScores($uploader_id, $key) {
9690        // get subcategory of current textboook used
9691        if ($key == 'narrate') {
9692            $key = 'read';
9693        }
9694        $onAir = $this->LessonOnair->find('first', array(
9695            'fields' => array('TextbookConnect.subcategory_id'),
9696            'joins' => array(
9697                array(
9698                    'type' => 'LEFT',
9699                    'table' => 'textbook_connects',
9700                    'alias' => 'TextbookConnect',
9701                    'conditions' => 'TextbookConnect.id = LessonOnair.connect_id'
9702                )
9703            ),
9704            'conditions' => array('LessonOnair.teacher_id' => $uploader_id),
9705            'recursive' => -1
9706        ));
9707        $subCategoryId = isset($onAir['TextbookConnect']['subcategory_id']) && !empty($onAir['TextbookConnect']['subcategory_id']) ? $onAir['TextbookConnect']['subcategory_id'] : Configure::read('dynamic_textbook_subcategory.eiken_grade_3');
9708        $dynamicTextbookSeries = Configure::read('dynamic_textbook_subcategory');
9709        $dynamicTextbookCourses = Configure::read('dynamic_textbook_subcategory_course');
9710        if (in_array($subCategoryId, $dynamicTextbookCourses)){
9711            // get series equivalent
9712            $subCategoryId =  $dynamicTextbookSeries[array_search($subCategoryId,$dynamicTextbookCourses)];
9713        }
9714        // get memcached data
9715        if (!class_exists('myMemcached',false)) { App::uses('myMemcached', 'Lib'); }
9716        $memcached = new myMemcached();
9717        $getMemKey = 'textbook_equivalent_score_'.$subCategoryId;
9718        $rawEquivalentScores = $memcached->get($getMemKey);
9719
9720        // fetch from DB if memcached does not exist
9721        if (!$rawEquivalentScores){
9722            $this->loadModel('ChivoxTextbookEquivalentScore');
9723            $result = $this->ChivoxTextbookEquivalentScore->find('first', array(
9724                'fields' => array('equivalent_scores'),
9725                'conditions' => array(
9726                    "textbook_subcategory_id" => $subCategoryId
9727                )
9728            ));
9729            $rawEquivalentScores = isset($result['ChivoxTextbookEquivalentScore']['equivalent_scores']) && !empty($result['ChivoxTextbookEquivalentScore']['equivalent_scores']) ? $result['ChivoxTextbookEquivalentScore']['equivalent_scores'] : "";
9730            if (!empty($rawEquivalentScores)) {
9731                $memcached->set(array(
9732                    'key' => $getMemKey,
9733                    'value' => $rawEquivalentScores,
9734                    'expire' => 1209600 // 2 weeks
9735                ));
9736            }
9737        }
9738
9739        $rawScore = json_decode($rawEquivalentScores, true);
9740        $getTargeyKey = isset($rawScore[$key]) ? $rawScore[$key] : array();
9741
9742        return $getTargeyKey;
9743    }
9744
9745    /**
9746     * @api {post} /teacher/api/confirmSuddenLesson confirmSuddenLesson()
9747     * @apiName confirmSuddenLesson
9748     * @apiGroup API
9749     * @apiDescription Confirm sudden lesson
9750     * @apiSampleRequest off
9751     * 
9752     * @apiBody {String} teacher_id The teacher ID.
9753     * 
9754     * @apiSuccess {Array} Teacher The teacher data.
9755     * @apiSuccess {String} Teacher.id The teacher ID.
9756     * @apiSuccess {String} Teacher.rank_coin_id The rank coin ID.
9757     * @apiSuccess {String} Teacher.current_rank_id The current rank ID.
9758     * @apiSuccess {String} Teacher.reserve_coin_data_id The reserve coin data ID.
9759     * @apiSuccess {String} Teacher.memo The memo.
9760     * 
9761     * @apiErrorExample {json} Success-Response:
9762     *     {
9763     *       "Teacher": {
9764     *           "id": 1,
9765     *           "rank_coin_id": 19,
9766     *           "current_rank_id": 2,
9767     *           "reserve_coin_data_id": 3,
9768     *           "memo": "2023/10/01\nTutor Category 22→19 because teacher requested for \"Sudden Class\"\n\nOld memo"
9769     *       }
9770     *     }
9771     *
9772      * @apiErrorExample {js} Used in: AngularJS
9773      * Location: "webroot/js/ng/controller/header.js"
9774     */
9775    public function confirmSuddenLesson() {
9776        $this->autoRender = false;
9777        if ($this->request->is('post')) {
9778            $teacherID = $this->request->data['teacher_id'];
9779
9780            if ($teacherID) {
9781                $this->Teacher->openDBReplica();
9782                $teacherData = $this->Teacher->find('first', array(
9783                    'fields' => array(
9784                        'Teacher.rank_coin_id',
9785                        'Teacher.current_rank_id',
9786                        'Teacher.reserve_coin_data_id',
9787                        'Teacher.memo'
9788                    ),
9789                    'conditions' => array(
9790                        'Teacher.id' => $teacherID
9791                    ),
9792                    'recursive' => -1
9793                ));
9794                $this->Teacher->closeDBReplica();
9795
9796                if ($teacherData) {
9797                    $updateData = array();
9798                    $currentDate = date('Y/m/d');
9799                    $teacherRankCoinID = $teacherData['Teacher']['rank_coin_id'];
9800                    $reserveCoinDataID = $teacherData['Teacher']['reserve_coin_data_id'];
9801                    $oldMeno = $teacherData['Teacher']['memo'];
9802
9803                    // check if teacher is allowed to apply sudden lesson
9804                    $allowedApplySuddenLesson = Configure::read('allowed_apply_sudden_lesson');
9805                    if (!in_array($teacherRankCoinID, $allowedApplySuddenLesson)) {
9806                        return;
9807                    }
9808
9809                    // change rank_coin_id
9810                    if ($teacherRankCoinID == 22) {
9811                        $updateData['rank_coin_id'] = 19;
9812                    } else if ($teacherRankCoinID == 58) {
9813                        $updateData['rank_coin_id'] = 57;
9814                    } else if ($teacherRankCoinID == 72) {
9815                        $updateData['rank_coin_id'] = 73;
9816                    }
9817
9818                    // change current_rank_id
9819                    $this->TeacherRankCoin->openDBReplica();
9820                    $currentRankID = $this->TeacherRankCoin->find('first', array(
9821                        'fields' => array('TeacherRankCoin.tutor_default_rank'),
9822                        'conditions' => array('TeacherRankCoin.id' => $updateData['rank_coin_id']),
9823                        'recursive' => -1
9824                    ));
9825                    $this->TeacherRankCoin->closeDBReplica();
9826                    $updateData['current_rank_id'] = $currentRankID ? $currentRankID['TeacherRankCoin']['tutor_default_rank'] : null;
9827
9828                    // change reserve_coin_data_id
9829                    $reserveCoinDataArray = $this->HomeBasedReserveCoinData->getCoinDataList($updateData['rank_coin_id'], $updateData['current_rank_id']);
9830                    if ($reserveCoinDataArray) {
9831                        $ids = array_column($reserveCoinDataArray, 'id');
9832                        if (empty($reserveCoinDataID) || !in_array($reserveCoinDataID, $ids)) {
9833                            $updateData['reserve_coin_data_id'] = end($ids);
9834                        } else {
9835                            $updateData['reserve_coin_data_id'] = $reserveCoinDataID;
9836                        }
9837                    } else {
9838                        $updateData['reserve_coin_data_id'] = null;
9839                    }
9840
9841                    // add memo
9842                    $updateData['memo'] = $currentDate."\nTutor Category ".$teacherRankCoinID.'→'.$updateData['rank_coin_id'].' because teacher requested for "Sudden Class"'."\n\n".$oldMeno;
9843
9844                    // when changing teacher rank_coin_id
9845                    if ((int)$teacherData['Teacher']['rank_coin_id'] !== (int)$updateData['rank_coin_id']) {
9846                        $hbfsParams = array(
9847                            'teacherId' => $teacherID,
9848                            'periodDate' => myTools::getCutoffDate(date('Y-m-d', strtotime('now'))),
9849                            'rankCoinSetting' => $this->TeacherRankCoin->getRankCoinSettings($updateData['rank_coin_id']),
9850                            'newRankCoin' => $updateData['rank_coin_id'],
9851                            'wuBankDetailTypeChange' => false,
9852                            'updateFinalBankDetailType' => null
9853                        );
9854                        $this->HomeBasedFinalSalaryData->teacherChangeRankCoinAndBankDetailType($hbfsParams);
9855                    }
9856
9857                    // when changing teacher current_rank_id
9858                    if ((int)$teacherData['Teacher']['current_rank_id'] !== (int)$updateData['current_rank_id']) {
9859                        $cutoffPeriod = myTools::hbGetCutoffDates($currentDate);
9860
9861                        $this->HomeBasedRankBasicAmount->openDBReplica();
9862                        $getRankAmountId = $this->HomeBasedRankBasicAmount->find('first', array(
9863                            'fields' => array(
9864                                'HomeBasedRankBasicAmount.id'
9865                            ),
9866                            'conditions' => array(
9867                                'HomeBasedRankBasicAmount.rank_id' => $updateData['current_rank_id']
9868                            ),
9869                            'recursive' => -1
9870                        ));
9871                        $this->HomeBasedRankBasicAmount->closeDBReplica();
9872
9873                        if ($getRankAmountId) {
9874                            $teacherRankDataLog = array(
9875                                'basic_amount_log_id' => $getRankAmountId['HomeBasedRankBasicAmount']['id'],
9876                                'teacher_id' => $teacherID,
9877                                'rank_id' => $updateData['current_rank_id'],
9878                                'period_date' => myTools::getCutoffDate($cutoffPeriod['periodDate'])
9879                            );
9880
9881                            $this->HomeBasedTeacherRankLog->clear();
9882                            $this->HomeBasedTeacherRankLog->create();
9883                            $this->HomeBasedTeacherRankLog->set($teacherRankDataLog);
9884                            $this->HomeBasedTeacherRankLog->save();
9885                        }
9886
9887                    }
9888
9889                    // when changing teacher reserve_coin_data_id
9890                    if((int)$teacherData['Teacher']['reserve_coin_data_id'] !== (int)$updateData['reserve_coin_data_id']){
9891                        $teacherSettings = $this->Teacher->getReserveCoinData($teacherID);
9892                        $teacherSettings['Teacher']['rank_coin_id'] = $updateData['rank_coin_id'];
9893                        $teacherSettings['Teacher']['current_rank_id'] = $updateData['current_rank_id'];
9894                        $newReserveCoinId = $updateData['reserve_coin_data_id'];
9895
9896                        $this->Teacher->setReserveCoinData($teacherSettings, $newReserveCoinId, true, false);
9897                    }
9898
9899                    $this->Teacher->clear();
9900                    $this->Teacher->read(array_keys($updateData), $teacherID);
9901                    $this->Teacher->set($updateData);
9902                    $updatedTeacher = $this->Teacher->save();
9903                    
9904                    if ($updatedTeacher) {
9905                        return json_encode($updatedTeacher);
9906                    }
9907                }
9908            }
9909        } else {
9910            return;
9911        }
9912    }
9913    
9914    /**
9915     * @api {post} /teacher/api/postEventCampaign postEventCampaign()
9916     * @apiName postEventCampaign
9917     * @apiGroup API
9918     * @apiDescription Post event campaign
9919     * @apiSampleRequest off
9920     * 
9921     * @apiBody {String} event_campaign_id The event campaign ID.
9922     * @apiBody {String} voted_name The voted name.
9923     * @apiBody {String} teacher_id The teacher ID.
9924     * 
9925     * @apiSuccess {Boolean} error The status of the request.
9926     * @apiSuccess {String} msg The message.
9927     * 
9928     * @apiErrorExample {json} Success-Response:
9929     *     {
9930     *       "error": false
9931     *     }
9932     *
9933     * @apiErrorExample {json} Error-Response:
9934     *     {
9935     *       "error": true,
9936     *       "msg": "Teacher already voted."
9937     *     }
9938     * 
9939      * @apiErrorExample {js} Used in: AngularJS
9940      * Location: "webroot/js/ng/app.js"
9941     */
9942    //NJ-11612
9943    public function postEventCampaign() {
9944        $res = array('error' => true);
9945        $this->autoRender = false;
9946
9947        if ($this->request->is('ajax')) {
9948            $postData = $this->request->data;
9949            if (
9950                (!isset($postData['event_campaign_id']) || !$postData['event_campaign_id'])
9951                || (!isset($postData['voted_name']) || !$postData['voted_name'])
9952                || (!isset($postData['teacher_id']) || !$postData['teacher_id'])
9953            ) {
9954                return json_encode($res);
9955            }
9956
9957            //check if teacher is exists
9958            $this->Teacher->recursive = -1;
9959            $teacherData = $this->Teacher->findById($postData['teacher_id']);
9960            if ($teacherData) {
9961                $eventPostData = array(
9962                    'event_campaign_id' => $postData['event_campaign_id'],
9963                    'teacher_id' => $this->Auth->User('id'),
9964                    'status' => 1,
9965                    'question1' => 'Teacher',
9966                    'question2' => $postData['voted_name']
9967                );
9968
9969                $eventCampaign = ClassRegistry::init('EventCampaign');
9970                //Check teacher if already posted/voted
9971                $userEventPosts = $eventCampaign->countEventPostsPerUser(
9972                    array(
9973                        'event_campaign_id' => $postData['event_campaign_id'],
9974                        'teacher_id' => $postData['teacher_id'],
9975                        'status' => 1
9976                    )
9977                );
9978                if($userEventPosts) {
9979                    $res['msg'] = 'Teacher already voted.';
9980                    return json_encode($res);
9981                }
9982
9983                $eventCampaign->clear();
9984                $eventCampaign->set($eventPostData);
9985                if ($eventCampaign->save()) {
9986                    $res['error'] = false; 
9987                }
9988            }
9989        }
9990
9991        return json_encode($res);
9992    }
9993
9994    /**
9995     * @api {get} /teacher/api/getTeachinMaterialEngName getTeachinMaterialEngName()
9996     * @apiName getTeachinMaterialEngName
9997     * @apiGroup API
9998     * @apiDescription Get textbook english name
9999     * @apiSampleRequest off
10000     * 
10001     * @apiBody {String} connect_id The connect ID.
10002     * 
10003     * @apiSuccess {String} textbookName The textbook name.
10004     * 
10005     * @apiErrorExample {json} Success-Response:
10006     * "Category Subcategory : Textbook"
10007     */
10008    /**
10009     * get textbook english name 
10010     * connect_id - required
10011     * @return mixed textbookName string
10012     */
10013    public function getTeachinMaterialEngName() {
10014        $this->autoRender = false;
10015        $textbookName = '';
10016        if ( !empty($this->request->query['connect_id']) ) {
10017            $textbookNameRaw = $this->TextbookConnect->getTextbookName(array(
10018                'connect_id' => $this->request->query['connect_id'],
10019                'native_language' => 'en'
10020            ));            
10021            $textbookName .= !empty($textbookNameRaw['textbook_category_name']) ? $textbookNameRaw['textbook_category_name'] : '';
10022            $textbookName .= !empty($textbookNameRaw['textbook_subcategory_name']) ? ' ' . $textbookNameRaw['textbook_subcategory_name'] : '';
10023            $textbookName .= !empty($textbookNameRaw['textbook_name']) ? ' : ' . $textbookNameRaw['textbook_name'] : '';
10024        }
10025        return $textbookName;        
10026    }
10027
10028    /**
10029     * @api {get} /teacher/api/teacherAudioDataMemCached teacherAudioDataMemCached()
10030     * @apiName teacherAudioDataMemCached
10031     * @apiGroup API
10032     * @apiDescription Teacher audio data memcached
10033     * @apiSampleRequest off
10034     * 
10035     * @apiBody {String} chat_hash The chat hash.
10036     * 
10037     * @apiSuccess {Boolean} success The status of the request.
10038     * @apiSuccess {String} type The type of the request.
10039     * @apiSuccess {Array} value The value of the request.
10040     * @apiSuccess {String} value.teacher_audio_data_percentage The teacher audio data percentage.
10041     * @apiSuccess {String} value.teacher_audio_interval The teacher audio interval.
10042     * 
10043     * @apiErrorExample {json} Success-Response:
10044     *     {
10045     *       "success": true,
10046     *       "type": "update",
10047     *       "value": {
10048     *           "teacher_audio_data_percentage": 50,
10049     *           "teacher_audio_interval": 10
10050     *       }
10051     *     }
10052     *
10053     * @apiErrorExample {js} Used in: Elements
10054     * Location: "view/Elements/chat_area_js.php"
10055     */
10056    /**
10057    *  // Check if teacher has a recent teacher audio data memcached if none ->add if has ->update
10058    * // get memcache data by key
10059    * @return bool
10060    */
10061    public function teacherAudioDataMemCached () {
10062        $this->layout = $this->autoRender = false;
10063        $chatHash = (isset($this->request->data['chat_hash'])) ? $this->request->data['chat_hash'] : null;
10064        $teacherAudioData = (isset($this->request->data['teacher_audio_data'])) ? $this->request->data['teacher_audio_data'] : 0;
10065        $teacherAudioInterval = (isset($this->request->data['teacher_audio_interval'])) ? $this->request->data['teacher_audio_interval'] : 0;
10066        $isDelete = (isset($this->request->data['is_delete'])) ? $this->request->data['is_delete'] : false;
10067        $res = array('success' => false);
10068
10069        //include if not exist
10070        if (!class_exists('myMemcached')) {
10071            App::uses('myMemcached', 'Lib');
10072        }
10073
10074        $memcached = new myMemcached();
10075        $memKey = 'teacher_audio_data_acquisition_' . $chatHash;
10076        $memKey2 = 'teacher_audio_interval_' . $chatHash;
10077        $memTeacherAudioData = $memcached->get($memKey);
10078        $memTeacherInterval = $memcached->get($memKey2);
10079
10080        //creating or updating memcache
10081        if ($chatHash && $teacherAudioData) {
10082            $this->log("kevin trigger ", "debug");
10083            if ($memTeacherAudioData) {
10084                $memcached->set(array(
10085                    'key' => $memKey,
10086                    'value' => $teacherAudioData,
10087                ));
10088                $memcached->set(array(
10089                    'key' => $memKey2,
10090                    'value' => $teacherAudioInterval,
10091                ));
10092                $res = array(
10093                    'success' => true,
10094                    'type' => 'update',
10095                    'value' =>  array(
10096                        'teacher_audio_data_percentage' => $teacherAudioData,
10097                        'teacher_audio_interval' => $teacherAudioInterval,
10098                    )
10099                );
10100            } else {
10101                $memcached->set(array(
10102                    'key' => $memKey,
10103                    'value' => $teacherAudioData,
10104                    'expire' => 1800 // 30 mins
10105                ));
10106                $memcached->set(array(
10107                    'key' => $memKey2,
10108                    'value' => $teacherAudioInterval,
10109                    'expire' => 1800 // 30 mins
10110                ));
10111                $res = array(
10112                    'success' => true,
10113                    'type' => 'create',
10114                    'value' =>  array(
10115                        'teacher_audio_data_percentage' => $teacherAudioData,
10116                        'teacher_audio_interval' => $teacherAudioInterval,
10117                    )
10118                );
10119            }
10120        } else if ($chatHash && $isDelete) { //for deletion of memcache data
10121            $memcached->delete($memKey);
10122            $memcached->delete($memKey2);
10123            $res = array('success' => true);
10124        }
10125        
10126        return json_encode($res);    
10127    }
10128
10129    /**
10130     * @api {get} /teacher/api/nextLessonReservation nextLessonReservation()
10131     * @apiName nextLessonReservation
10132     * @apiGroup API
10133     * @apiDescription Get next lesson reservation
10134     * @apiSampleRequest off
10135     * 
10136     * @apiSuccess {Boolean} nowStatus The status of the request.
10137     * @apiSuccess {Boolean} nowStatusRemarks The status remarks of the request.
10138     * @apiSuccess {Array} next_lesson_reservations The next lesson reservations.
10139     * @apiSuccess {String} next_lesson_reservations.id The ID.
10140     * @apiSuccess {String} next_lesson_reservations.lesson_time The lesson time.
10141     * @apiSuccess {String} next_lesson_reservations.profile The profile.
10142     * @apiSuccess {String} next_lesson_reservations.nickname The nickname.
10143     * @apiSuccess {String} next_lesson_reservations.start_time The start time.
10144     * @apiSuccess {String} next_lesson_reservations.end_time The end time.
10145     * 
10146     * @apiErrorExample {json} Success-Response:
10147     *     {
10148     *       "nowStatus": 5,
10149     *       "nowStatusRemarks": 1,
10150     *       "next_lesson_reservations": [
10151     *           {
10152     *               "id": 1,
10153     *               "lesson_time": "2023-10-01 10:00:00",
10154     *               "profile": "url",
10155     *               "nickname": "John",
10156     *               "start_time": "Oct 01 10:00AM",
10157     *               "end_time": "10:30AM"
10158     *           }
10159     *       ]
10160     *     }
10161     *
10162     * @apiErrorExample {json} Error-Response:
10163     *     {
10164     *       "nowStatus": false,
10165     *       "nowStatusRemarks": false,
10166     *       "next_lesson_reservations": []
10167     *     }
10168     * 
10169      * @apiErrorExample {js} Used in: AngularJS
10170      * Location: "webroot/js/ng/controller/home.js"
10171      * Location: "webroot/js/ng/app.js"
10172      * Location: "webroot/js/ng/home.js"
10173     */
10174    public function nextLessonReservation(){
10175        $this->autoRender = false;
10176        $view = new View($this, false);
10177        
10178        $teacher_id = $this->Auth->user('id');
10179
10180        if(empty($teacher_id) || !$teacher_id){
10181            return false;
10182        }
10183        $startDate = date('Y-m-d H:i:s');
10184        $schedules = array(); // set default as empty
10185        $result = array(); // set default as empty
10186
10187        $nowStatus = !empty($this->Session->read('Teacher.status')) ? $this->Session->read('Teacher.status') : false;
10188        $nowStatusRemarks = !empty($this->Session->read('Teacher.remarks')) ? $this->Session->read('Teacher.remarks') : false;
10189
10190        $lsParams = array(
10191            'args' => array(
10192                'fields' => array(
10193                    'LessonSchedule.lesson_time',
10194                    'User.nickname',
10195                    'User.id',
10196                    'User.image_url',
10197                    'User.profile_image',
10198                    'ShiftWorkon.do_live_flg',
10199                    'ShiftWorkon.status',
10200                    'ShiftWorkon.live_lesson_flg'
10201                ),
10202                'joins' => array(
10203                    array(
10204                        'table' => 'shift_workons',
10205                        'alias' => 'ShiftWorkon',
10206                        'type' => 'LEFT',
10207                        'conditions' => array(
10208                            'ShiftWorkon.teacher_id = LessonSchedule.teacher_id',
10209                            'ShiftWorkon.lesson_time = LessonSchedule.lesson_time'
10210                        )
10211                    )
10212                ),
10213                'conditions' => array(
10214                    'LessonSchedule.teacher_id' => $teacher_id,
10215                    'LessonSchedule.status' => 1,
10216                    'LessonSchedule.lesson_time >=' => $startDate,
10217                    'User.status' => 1
10218                ),
10219                'order' => 'LessonSchedule.lesson_time ASC',
10220                'limit' => 2
10221            )
10222        );
10223
10224        // NJ-64088
10225        $schedules = $this->LessonSchedule->getSchedules($lsParams);
10226        
10227        if (is_array($schedules) && $schedules) {
10228            foreach($schedules as $key=>$schedule){
10229                $user = new UserTable($schedule['User']);
10230                $nickname = $schedule['User']['nickname'];
10231                $nickname = strlen($nickname) > 20 ? trim(substr($nickname,0,20))."..." : $nickname;
10232                $start_time = date('Md g:i A',strtotime($this->timeDiff.' minutes'.$schedule['LessonSchedule']['lesson_time']));
10233                $end_time = date('g:i A',strtotime($this->timeDiff.' minutes'.$schedule['LessonSchedule']['lesson_time'].' +30 minutes'));
10234
10235                // Check if the dates are the same
10236                $is_live = $schedule['ShiftWorkon']['live_lesson_flg'] == 1
10237                && $schedule['ShiftWorkon']['do_live_flg'] == 1
10238                && $schedule['ShiftWorkon']['status'] == 1
10239                ? 1 : 0;
10240
10241                $result[$key]['id'] = $schedule['User']['id'];
10242                $result[$key]['lesson_time'] = $schedule['LessonSchedule']['lesson_time'];
10243                $result[$key]['profile'] = $user->getImageUrl();
10244                $result[$key]['nickname'] = $nickname;
10245                $result[$key]['start_time'] = $start_time;
10246                $result[$key]['end_time'] = $end_time;
10247                $result[$key]['is_live'] = $is_live;
10248            }
10249        }
10250
10251        return json_encode(array('nowStatus'=>$nowStatus,'nowStatusRemarks'=>$nowStatusRemarks,'next_lesson_reservations'=>$result));
10252    }
10253
10254    /**
10255     * @api {post} /teacher/api/otherAudioDataMemCached otherAudioDataMemCached()
10256     * @apiName otherAudioDataMemCached
10257     * @apiGroup API
10258     * @apiDescription Other audio data memcached
10259     * @apiSampleRequest off
10260     * 
10261     * @apiBody {String} chat_hash The chat hash.
10262     * @apiBody {String} teacher_audio_data_percentage The teacher audio data percentage.
10263     * @apiBody {String} student_audio_data_percentage The student audio data percentage.
10264     * 
10265     * @apiSuccess {Boolean} success The status of the request.
10266     * @apiSuccess {String} type The type of the request.
10267     * @apiSuccess {Array} values The values of the request.
10268     * @apiSuccess {String} values.teacher_audio_data_percentage The teacher audio data percentage.
10269     * @apiSuccess {String} values.student_audio_data_percentage The student audio data percentage.
10270     * 
10271     * @apiErrorExample {json} Success-Response:
10272     *     {
10273     *       "success": true,
10274     *       "type": "update",
10275     *       "values": {
10276     *           "teacher_audio_data_percentage": 50,
10277     *           "student_audio_data_percentage": 50
10278     *       }
10279     *     }
10280     * 
10281     * @apiErrorExample {js} Used in: Elements
10282     * Location: "view/Elements/chat_area_js.php"
10283     */
10284    /**
10285    *  // Check if teacher has a recent teacher audio data memcached if none ->add if has ->update
10286    * // get memcache data by key
10287    * @return bool
10288    */
10289    public function otherAudioDataMemCached () {
10290        $this->layout = $this->autoRender = false;
10291        $chatHash = (isset($this->request->data['chat_hash'])) ? $this->request->data['chat_hash'] : null;
10292        $studentAudioDataPercentage = (isset($this->request->data['student_audio_data_percentage'])) ? $this->request->data['student_audio_data_percentage'] : 0;
10293        $teacherAudioDataPercentage = (isset($this->request->data['teacher_audio_data_percentage'])) ? $this->request->data['teacher_audio_data_percentage'] : 0;
10294        $isDelete = (isset($this->request->data['is_delete'])) ? $this->request->data['is_delete'] : false;
10295        $res = array('success' => false);
10296
10297        //include if not exist
10298        if (!class_exists('myMemcached')) {
10299            App::uses('myMemcached', 'Lib');
10300        }
10301
10302        $memcached = new myMemcached();
10303
10304        $memKey = 'teacher_audio_data_percentage_' .$chatHash;
10305        $memKey2 = 'student_audio_data_percentage_' .$chatHash;
10306        $memTeacherAudioDataPercentage = $memcached->get($memKey);
10307        $memStudentAudioDataPercentage = $memcached->get($memKey2);
10308
10309        if($chatHash && $teacherAudioDataPercentage != null && $studentAudioDataPercentage != null) {
10310            if ($memTeacherAudioDataPercentage && $memStudentAudioDataPercentage) {
10311                $memcached->set(array(
10312                    'key' => $memKey,
10313                    'value' => $teacherAudioDataPercentage,
10314                ));
10315                $memcached->set(array(
10316                    'key' => $memKey2,
10317                    'value' => $studentAudioDataPercentage,
10318                ));
10319                $res = array(
10320                    'success' => true,
10321                    'type' => 'update',
10322                    'values' => array(
10323                        'teacher_audio_data_percentage' => $teacherAudioDataPercentage,
10324                        'student_audio_data_percentage' => $studentAudioDataPercentage
10325                    )
10326                );
10327            } else {
10328                $memcached->set(array(
10329                    'key' => $memKey,
10330                    'value' => $teacherAudioDataPercentage,
10331                    'expire' => 1800 // 30 mins
10332                ));
10333                $memcached->set(array(
10334                    'key' => $memKey2,
10335                    'value' => $studentAudioDataPercentage,
10336                    'expire' => 1800 // 30 mins
10337                ));
10338                $res = array(
10339                    'success' => true,
10340                    'type' => 'create',
10341                    'values' => array(
10342                        'teacher_audio_data_percentage' => $teacherAudioDataPercentage,
10343                        'student_audio_data_percentage' => $studentAudioDataPercentage
10344                    )
10345                );
10346            }
10347        } else if ($chatHash && $isDelete) { //for deletion of memcache data
10348            $memcached->delete($memKey);
10349            $memcached->delete($memKey2);
10350            $res = array('success' => true);
10351        }
10352        
10353        return json_encode($res);
10354    }
10355
10356    /**
10357     * @api {get} /teacher/api/fcmWebNotification fcmWebNotification()
10358     * @apiName fcmWebNotification
10359     * @apiGroup API
10360     * @apiDescription Send web push notification to idle teachers
10361     * @apiSampleRequest off
10362     * 
10363     * @apiBody {String} device_token The device token.
10364     * @apiBody {String} target_url The target URL.
10365     * 
10366     * @apiSuccess {Boolean} success The status of the request.
10367     * @apiSuccess {Array} fcm_response The FCM response. (`$fcmApp->curlFCM($payload, $appServerKey)`)
10368     * 
10369     * @apiErrorExample {json} Success-Response:
10370     *     {
10371     *       "success": true,
10372     *       "fcm_response": {...}
10373     *     }
10374     *
10375      * @apiErrorExample {js} Used in: JS
10376      * Location: "webroot/js/custom_sound-recorder.js"
10377      * Location: "webroot/js/teacher-fcm-notif-config.js"
10378     */
10379    /*
10380     * Send web push notification to idle teachers
10381     * @param device_token - required
10382     * @return res json
10383     */ 
10384    public function fcmWebNotification(){
10385        $this->autoRender = false;
10386        $res = array('success' => false);
10387        if (
10388            $this->request->is('ajax')
10389            && !empty($this->request->data['device_token'])
10390            && !empty($this->request->data['target_url'])
10391        ) {
10392            $fcmApp = new firebaseCloudMsg();
10393            $appServerKey = Configure::read('fdcm_web.server_key');
10394            $payload = array(
10395                'to' => $this->request->data['device_token'],
10396                'data' => array(
10397                    'title' => 'test',
10398                    'body' => 'test'
10399                ),
10400                'notification' => array(
10401                    'title' => 'Native Camp',
10402                    'body' => 'Your status has been changed to NOT STANDBY',
10403                    'content_available' => true,
10404                    'click_action' => $this->request->data['target_url'],
10405                    'icon' => 'teacher/images/common/nc_logo.png'
10406                ),
10407                'webpush' => array(
10408                    'fcm_options' => array()
10409                )
10410            );
10411            $fcmPush = $fcmApp->curlFCM($payload, $appServerKey);
10412            $res['success'] = true;
10413            $res['fcm_response'] = $fcmPush;
10414        }
10415        return json_encode($res);
10416    }
10417
10418    /**
10419     * @api {post} /teacher/api/studentLeftLessonUpdate studentLeftLessonUpdate()
10420     * @apiName studentLeftLessonUpdate
10421     * @apiGroup API
10422     * @apiDescription Update student left lesson
10423     * @apiSampleRequest off
10424     * 
10425     * @apiBody {String} chat_hash The chat hash.
10426     * @apiBody {String} user_id The user ID.
10427     * 
10428     * @apiSuccess {Boolean} success The status of the request.
10429     * 
10430     * @apiErrorExample {json} Success-Response:
10431     *     {
10432     *       "success": true
10433     *     }
10434     *
10435     * @apiErrorExample {json} Error-Response:
10436     *     {
10437     *       "success": false
10438     *     }
10439     */
10440    /**
10441     * [NJ-18003] if student left the reseved lesson, current use/call from reserved live lesson
10442     */
10443    public function studentLeftLessonUpdate() {
10444        $this->autoRender = false;
10445        $res = ['success' => false];
10446
10447        if ($this->request->is('ajax') && !empty($this->request->data['chat_hash']) && !empty($this->request->data['user_id'])) {
10448            $onAir = $this->LessonOnair->find('first', [
10449                'fields' => [
10450                    'LessonOnair.id', 
10451                    'LessonOnair.lesson_type', 
10452                    'LessonOnair.leave_lesson',
10453                    'LessonOnair.user_id'
10454                ],
10455                'conditions' => [
10456                    'LessonOnair.chat_hash' => trim($this->request->data['chat_hash'])
10457                ],
10458                'recursive' => -1
10459            ]);
10460
10461            if(
10462                $onAir && 
10463                $onAir['LessonOnair']['lesson_type'] == Configure::read('lesson.type.reservation') &&
10464                $onAir['LessonOnair']['user_id'] == $this->request->data['user_id']
10465            ) {
10466                try {
10467                    $this->LessonOnair->read(['leave_lesson'], $onAir['LessonOnair']['id']);
10468                    $this->LessonOnair->saveField('leave_lesson', 1);
10469                    $res = ['success' => true];
10470                } catch (Exception $e) {
10471                    $this->log(__METHOD__. ' : [NJ-18003] leave_lesson update] -> ' . $e->getMessage(), 'debug');
10472                }
10473            }
10474        }
10475
10476        return json_encode($res);
10477    }
10478
10479    /**
10480     * @api {post} /teacher/api/updateCurrentTutorialModalSteps updateCurrentTutorialModalSteps()
10481     * @apiName updateCurrentTutorialModalSteps
10482     * @apiGroup API
10483     * @apiDescription Update current tutorial modal steps
10484     * @apiSampleRequest off
10485     * 
10486     * @apiBody {String} current The current step.
10487     * @apiBody {String} show The show step.
10488     * 
10489     * @apiSuccess {Boolean} status The status of the request.
10490     * @apiSuccess {String} msg The message.
10491     * @apiSuccess {Array} steps The steps. (`$memcached->get("modal_and_tutorial{$teacherID}")`)
10492     * 
10493     * @apiErrorExample {json} Success-Response:
10494     *     {
10495     *       "status": true,
10496     *       "msg": "Success message",
10497     *       "steps": {...}
10498     *     }
10499     *
10500     * @apiErrorExample {json} Error-Response:
10501     *     {
10502     *       "status": false
10503     *     }
10504     * 
10505      * @apiErrorExample {js} Used in: AngularJS
10506      * Location: "webroot/js/ng/services.js"
10507     */
10508    public function updateCurrentTutorialModalSteps(){
10509        $this->layout = $this->autoRender = false;
10510        $response = array('status' => false);
10511
10512        // To reset the modal
10513        if(isset($_GET['reset']) && $_GET['reset'] == 1){
10514            $teacherID = $this->Auth->user('id');
10515            $memcached = new myMemcached();
10516            echo '<pre>';
10517            print_r($memcached->get("modal_and_tutorial{$teacherID}"));
10518            print_r($memcached->delete("modal_and_tutorial{$teacherID}"));
10519            echo "Teacher : {$teacherID} MODAL & TUTORIAL RESET!!";
10520            return;
10521        }
10522        
10523        if ($this->request->is('post')) {
10524            $reqData = $this->request->data;
10525            $teacherID = $this->Auth->user('id');
10526
10527            if(!empty($teacherID) && isset($reqData['current']) && isset($reqData['show'])){
10528                $memcached = new myMemcached();
10529                $updateModalTutorial = $memcached->set(array(
10530                    'key' => "modal_and_tutorial{$teacherID}",
10531                    'value' => array(
10532                        'current' => $reqData['current'], # == 1 - Modal A | 2 - Tutorial 1 | 3 - Modal B | 4 - Tutorial 2 | 5 - Modal C | 6 - Tutorial 3 ===
10533                        'show' => $reqData['show']
10534                    ), 
10535                    'expire' => 864000  # 10 days (10 * 24 * 3600 seconds)
10536                ));
10537
10538                $response['status'] = (is_array($updateModalTutorial) && $updateModalTutorial['error']) ? false : true;
10539                $response['msg'] = (is_array($updateModalTutorial)) ?? $updateModalTutorial['content'];
10540                $response['steps'] = $memcached->get("modal_and_tutorial{$teacherID}");
10541            }else{
10542                $response['status'] = false;
10543            }
10544        }
10545
10546        return json_encode($response);
10547    }
10548    
10549    /**
10550     * @api {get} /teacher/api/getUnviewedWarning getUnviewedWarning()
10551     * @apiName getUnviewedWarning
10552     * @apiGroup API
10553     * @apiDescription Get unviewed warning
10554     * @apiSampleRequest off
10555     * 
10556     * @apiSuccess {String} warningCount The warning count.
10557     * 
10558     * @apiErrorExample {json} Success-Response:
10559     *     {
10560     *       "warningCount": 3
10561     *     }
10562     *
10563      * @apiErrorExample {js} Used in: AngularJS
10564      * Location: "webroot/js/ng/controller/home.js"
10565      * Location: "webroot/js/ng/app.js"
10566      */
10567    public function getUnviewedWarning($id = 0) {
10568        $this->autoRender = false;
10569        $warningCount = $this->Warning->countReadWarning($id);
10570        return $warningCount;
10571    }
10572
10573    /**
10574     * @api {get} /teacher/api/showReservedLessonSpecialModal showReservedLessonSpecialModal()
10575     * @apiName showReservedLessonSpecialModal
10576     * @apiGroup API
10577     * @apiDescription Check if sapuri school user and show reserved lesson special modal(1 time only)
10578     * @apiSampleRequest off
10579     * 
10580     * @apiBody {String} userId The user ID.
10581     * @apiBody {String} lessonTime The lesson time.
10582     * 
10583     * @apiSuccess {Boolean} showModal The status of the request.
10584     * 
10585     * @apiErrorExample {json} Success-Response:
10586     *     {
10587     *       "showModal": true
10588     *     }
10589     *
10590      * @apiErrorExample {js} Used in: AngularJS
10591      * Location: "webroot/js/ng/app.js"
10592     */
10593    /**
10594     * Check if sapuri school user and show reserved lesson special modal(1 time only)
10595     */
10596    public function showReservedLessonSpecialModal($userId = null, $lessonTime = '') {
10597        $this->autoRender = false;
10598
10599        if (!isset($userId) || empty($lessonTime)){
10600            return false;
10601        }
10602
10603        $teacherId = $this->Auth->user('id');
10604        $memKey = 'tos_reserved_lesson_special_modal' . $userId . '_' . $teacherId . '_' . $lessonTime;
10605
10606        if (!class_exists('myMemcached')) {
10607            App::uses('myMemcached', 'Lib');
10608        }
10609
10610        $showModal = false;
10611        $memcached = new myMemcached();
10612        $memData = $memcached->get($memKey);
10613
10614        // check if memcache exist
10615        if (!$memData) {
10616            $showModal = true;
10617            $memcached->set([
10618                'key' => $memKey,
10619                'value' => true,
10620                'expire' => 1200 // 20 mins
10621            ]);
10622        }
10623
10624        return json_encode(['showModal' => $showModal]);
10625    }
10626
10627    /**
10628     * @api {post} /teacher/api/getLearningKitCount getLearningKitCount()
10629     * @apiName getLearningKitCount
10630     * @apiGroup API
10631     * @apiDescription Get learning kit count
10632     * @apiSampleRequest off
10633     * 
10634     * @apiSuccess {String} new_textbook_cnt The new textbook count.
10635     * 
10636     * @apiErrorExample {json} Success-Response:
10637     *     {
10638     *       "new_textbook_cnt": 5
10639     *     }
10640     * 
10641      * @apiErrorExample {js} Used in: AngularJS
10642      * Location: "webroot/js/ng/controller/home.js"
10643      * Location: "webroot/js/ng/app.js"
10644     */
10645    public function getLearningKitCount(){
10646        $this->autoRender = false;
10647        $teacherId = $this->Auth->user('id');
10648        $type = 1;
10649        $new_textbook_cnt = 0;
10650
10651        if($this->request->is('ajax')){
10652            $new_textbook_cnt = $this->TeacherBadge->getTeacherNewBadge(array('teacher_id' => $teacherId, 'type' => $type));
10653        }
10654
10655        return $new_textbook_cnt;
10656    }
10657
10658    /**
10659     * @api {post} /teacher/api/dynamicModalData dynamicModalData()
10660     * @apiName dynamicModalData
10661     * @apiGroup API
10662     * @apiDescription Get dynamic modal data
10663     * @apiSampleRequest off
10664     * 
10665     * @apiBody {String} user_id The user ID.
10666     * 
10667     * @apiSuccess {Boolean} status The status of the request.
10668     * @apiSuccess {Array} data The data.
10669     * @apiSuccess {String} data.lesson_count The lesson count.
10670     * @apiSuccess {String} data.member_type The member type.
10671     * @apiSuccess {String} data.member_index The member index.
10672     * @apiSuccess {String} data.trial_count The trial count.
10673     * @apiSuccess {String} data.withdrawalCounselingFlag The withdrawal counseling flag.
10674     * @apiSuccess {Array} data.onair The onair data. (`$this->LessonOnair->find`)
10675     * 
10676     * @apiErrorExample {json} Success-Response:
10677     *     {
10678     *       "status": true,
10679     *       "data": {
10680     *           "lesson_count": 10,
10681     *           "member_type": "Premium",
10682     *           "member_index": 2,
10683     *           "trial_count": 7,
10684     *           "withdrawalCounselingFlag": 1,
10685     *           "onair": {...}
10686     *       }
10687     *     }
10688     *
10689      * @apiErrorExample {js} Used in: AngularJS
10690      * Location: "webroot/js/ng/app.js"
10691     */
10692    # NJ-24697
10693    public function dynamicModalData(){
10694        $this->autoRender = false; # preventing from loading 404 hmtl
10695        $response = array('status' => false);
10696        $withdrawalCounselingFlag = 0;
10697        # check if the request is pot
10698        if($this->request->is('post')){
10699
10700            $data = $this->request->data;
10701            $counselor = $this->Auth->user('counseling_flg');
10702            
10703            if($counselor){
10704
10705                // check if not empty user id 
10706                $userID = (!empty($data["user_id"])) ? $data["user_id"] : null;
10707
10708                $this->LessonOnair->clear();
10709                $this->LessonOnair->openDBReplica();
10710                $onair = $this->LessonOnair->find('first',array(
10711                    'fields' => array(
10712                        "LessonOnair.user_id",
10713                        'LessonOnair.withdrawal_counseling_flg'
10714                    ),
10715                    'conditions' => array(
10716                        "LessonOnair.teacher_id" => $this->Auth->user('id')
10717                    ),
10718                    'recursive' => -1
10719                ));
10720                $this->LessonOnair->closeDBReplica();
10721
10722                $withdrawalCounselingFlag = (isset($onair['LessonOnair']['withdrawal_counseling_flg'])) ? $onair['LessonOnair']['withdrawal_counseling_flg'] : 0;
10723                $userID = !$userID ? (isset($onair['LessonOnair']['user_id']) ? $onair['LessonOnair']['user_id'] : $userID) : $userID;
10724
10725                $userData = $this->User->find('first', array(
10726                    'fields' => array(
10727                        'status',
10728                        'corporate_type',
10729                        'corporate_individual_payment_flg',
10730                        'fail_flg',
10731                        'double_check_flg',
10732                        'charge_flg',
10733                        'parent_id',
10734                        'payment_plan_id',
10735                        'id',
10736                        'hash16',
10737                        'studysapuri_id',
10738                        'corporate_id',
10739                        'nickname',
10740                        'created'
10741                    ),
10742                    'conditions' => array('id' => $userID),
10743                    'recursive' => -1
10744                ));
10745                
10746                $userTable = new UserTable($userData['User']);
10747
10748                $membershipTypeIndex = $userTable->getMembershipTypeIndex();
10749                $stdLesson = $this->LessonOnairsLog->countLessons($userID);
10750
10751                # calculate the remaining days of trial
10752                $startDate = new DateTime($userData["User"]["created"]);
10753                $currentDate = new DateTime();
10754                $interval = $startDate->diff($currentDate);
10755                $remainingDays = $interval->days > 7 ? 7 : $interval->days;
10756
10757                $response["status"] = true;
10758                $response["data"] = array(
10759                    "lesson_count" => $stdLesson,
10760                    "member_type" => UserTable::getJpnMembershipTypeData()[$membershipTypeIndex],
10761                    "member_index" => $membershipTypeIndex,
10762                    "trial_count" =>  $remainingDays,
10763                    "withdrawalCounselingFlag" => $withdrawalCounselingFlag,
10764                    'onair' => $onair
10765                );
10766            }
10767        }else{
10768            $response["status"] = false;
10769        }
10770 
10771        return json_encode($response); 
10772    }
10773
10774    /**
10775     * @api {post} /teacher/api/saveBookmark saveBookmark()
10776     * @apiName saveBookmark
10777     * @apiGroup API
10778     * @apiDescription Save bookmark data
10779     * @apiSampleRequest off
10780     * 
10781     * @apiBody {String} lesson_identifier The lesson identifier.
10782     * @apiBody {String} textbook_connect_id The textbook connect ID.
10783     * @apiBody {String} callan_bookmark_id The callan bookmark ID.
10784     * @apiBody {String} type The type.
10785     * 
10786     * @apiSuccess {Boolean} status The status of the request.
10787     * @apiSuccess {Array} data The data. (`$this->CallanBookmarkOption->find`)
10788     * 
10789     * @apiError {Object} error The error object.
10790     * @apiError {String} error.message The error message. 
10791     * 
10792     * @apiErrorExample {json} Success-Response:
10793     *     {
10794     *       "status": true,
10795     *       "data": {...}
10796     *     }
10797     *
10798     * @apiErrorExample {json} Error-Response:
10799     *     {
10800     *       "status": false,
10801     *       "error": {
10802     *           "message": "Invalid bookmark ID"
10803     *       }
10804     *     }
10805     * @apiErrorExample {js} Used in: JS
10806     * Location: "view/HtmlTextBook/index.php"
10807     */
10808    /*
10809    * NJ-33115: Save bookmark data
10810    */
10811    public function saveBookmark(){
10812        $this->autoRender = false;
10813        $response = array('status' => false);
10814
10815        if($this->request->is('post')){
10816            $chatHash = (isset($this->request->data['lesson_identifier'])) ? $this->request->data['lesson_identifier'] : null;
10817            $textbook_connect_id = (isset($this->request->data['textbook_connect_id'])) ? $this->request->data['textbook_connect_id'] : null;
10818            $callan_bookmark_string = (isset($this->request->data['callan_bookmark_id'])) ? $this->request->data['callan_bookmark_id'] : null;
10819            $type = (isset($this->request->data['type'])) ? $this->request->data['type'] : null;
10820            $category_id = isset($this->request->data['category_id']) ? $this->request->data['category_id'] : null;
10821
10822            // $bookmarkParts = explode('_', $callan_bookmark_string);
10823            // $callan_bookmark_id = end($bookmarkParts);
10824            $callan_bookmark_id = preg_replace('/[^0-9]/', '', $callan_bookmark_string);
10825
10826            // validate bookmark ID
10827            $this->CallanBookmarkOption->openDBReplica();
10828            $checkBookmarkData = $this->CallanBookmarkOption->find('first', array(
10829                'fields' => array(
10830                    'id',
10831                    'stage',
10832                    'page',
10833                    'type',
10834                    'headword',
10835                    'paragraph'
10836                ),
10837                'conditions' => array('id' => $callan_bookmark_id),
10838                'recursive' => -1
10839            ));
10840            $this->CallanBookmarkOption->closeDBReplica();
10841            
10842            if(isset($checkBookmarkData['CallanBookmarkOption']['stage'])){
10843                $this->CallanBookmarkOption->openDBReplica();
10844                $getTypeFormat = $this->CallanBookmarkOption->find('first', array(
10845                    'fields' => array(
10846                        "page", "type", "GROUP_CONCAT(paragraph ORDER BY paragraph SEPARATOR '|')  as paragraph"
10847                    ),
10848                    'conditions' => array(
10849                        'stage' => $checkBookmarkData['CallanBookmarkOption']['stage'],
10850                        'page' => $checkBookmarkData['CallanBookmarkOption']['page']
10851                    ),
10852                    'group' => 'page',
10853                    'recursive' => -1
10854                ));
10855                $this->CallanBookmarkOption->closeDBReplica();
10856            }
10857            
10858            // NJ-33801 - Change Type Format after bookmark
10859            $paragraphsInfo = !empty($getTypeFormat) ? explode('|', $getTypeFormat[0]['paragraph']) : null;
10860            
10861            if(!empty($paragraphsInfo)){
10862                $paragraphsInfo = explode('|', $getTypeFormat[0]['paragraph']);                
10863                $first = $paragraphsInfo[0];
10864                $last = end($paragraphsInfo);
10865                
10866                preg_match('/Lesson\s*(\d+)\s*,\s*(?:pp\s*(\d+)|(\d+))/i', $first, $firstMatch);
10867                preg_match('/Lesson\s*(\d+)\s*,\s*(?:pp\s*(\d+)|(\d+))/i', $last, $lastMatch);
10868                
10869                $lessonStart = $firstMatch[1];
10870                $lessonEnd = $lastMatch[1];
10871                $pageStart = $firstMatch[2];
10872                $pageEnd = $lastMatch[2];
10873                
10874                $lessonRange = ($lessonStart === $lessonEnd) ? "Lesson $lessonStart" : "Lesson $lessonStart~$lessonEnd";
10875                
10876                $pageRange = ($pageStart === $pageEnd) ? "pp$pageStart" : "pp$pageStart~$pageEnd";
10877            
10878                $formattedString = "".$checkBookmarkData['CallanBookmarkOption']['type']." ($lessonRange$pageRange)";
10879                
10880                $checkBookmarkData['CallanBookmarkOption']['formattedtype'] = $formattedString;    
10881            }else{
10882                $response['error']['message'] = "Paragraph Information is empty";
10883                return json_encode($response);
10884            }
10885
10886            if (!$checkBookmarkData) {
10887                $response['error']['message'] = "Invalid bookmark ID";
10888                return json_encode($response); 
10889            }
10890            
10891            // get student and teacher IDs
10892            $this->LessonOnair->openDBReplica();
10893            $onairData = $this->LessonOnair->find('first', array(
10894                'fields' => array(
10895                    'LessonOnair.user_id',
10896                    'LessonOnair.teacher_id',
10897                    'LessonOnair.connect_id'
10898                ),
10899                'conditions' => array('chat_hash' => $chatHash),
10900                'recursive' => -1
10901            ));
10902            $this->LessonOnair->closeDBReplica();
10903
10904            // validate callan lesson
10905            // TODO: add logic for outside lesson
10906            if(empty($onairData)){
10907
10908                $this->LessonOnairsLog->openDBReplica();
10909                $onairLogData = $this->LessonOnairsLog->find('first', array(
10910                    'fields' => array(
10911                        'LessonOnairsLog.user_id',
10912                        'LessonOnairsLog.teacher_id',
10913                        'LessonOnairsLog.connect_id',
10914                        'LessonOnairsLog.end_time'
10915                    ),
10916                    'conditions' => array('chat_hash' => $chatHash),
10917                    'recursive' => -1
10918                ));
10919                $this->LessonOnairsLog->closeDBReplica();
10920
10921                if(empty($onairLogData)){
10922                    $response['error']['message'] = "Cannot locate Callan Lesson Data";
10923                    $this->log("[saveBookmark] Cannot locate LessonOnairsLog: " . $chatHash, "error");
10924                    return json_encode($response); 
10925                }
10926
10927                // if greater than 3 minutes after lesson end
10928                $endTimeTimestamp = strtotime($onairLogData['LessonOnairsLog']['end_time']);
10929                $check3MinAfteLesson = (strtotime('now') <= $endTimeTimestamp + (3 * 60)) ? true : false; 
10930                if (!$check3MinAfteLesson) {
10931                    $response['error']['message'] = "Cannot bookmark after 3 minutes of lesson end";
10932                    $this->log("[saveBookmark] 3 minutes of lesson end: " . json_encode($onairLogData), "error");
10933                    return json_encode($response); 
10934                }
10935
10936                $user_id = $onairLogData['LessonOnairsLog']['user_id'];
10937                $teacher_id = $onairLogData['LessonOnairsLog']['teacher_id'];
10938                if (!isset($textbook_connect_id) || !$textbook_connect_id){
10939                    $textbook_connect_id = $onairLogData['LessonOnairsLog']['connect_id'];
10940                }
10941                
10942            } else {
10943                $user_id = $onairData['LessonOnair']['user_id'];
10944                $teacher_id = $onairData['LessonOnair']['teacher_id'];
10945                if (!isset($textbook_connect_id) || !$textbook_connect_id){
10946                    $textbook_connect_id = $onairData['LessonOnair']['connect_id'];
10947                }
10948            }
10949            
10950            $this->TextbookConnect->openDBReplica();
10951            $textbookConnectIds = $this->TextbookConnect->find('list', array(
10952                'fields' => array('id'),
10953                'conditions' => array('category_id' => $category_id),
10954                'recursive' => -1
10955            ));
10956            $this->TextbookConnect->closeDBReplica();
10957
10958            // get the current lesson boookmark
10959            $this->LessonBookmark->openDBReplica();
10960            $currentBookmark = $this->LessonBookmark->find('first', array(
10961                'fields' => array(
10962                    'LessonBookmark.id',
10963                    'LessonBookmark.callan_bookmark_id'
10964                ),
10965                'conditions' => array(
10966                    'LessonBookmark.user_id' => $user_id,
10967                    'LessonBookmark.textbook_connect_id IN ' => $textbookConnectIds,
10968                ),
10969                'recursive' => -1
10970            ));
10971            $this->LessonBookmark->closeDBReplica();
10972
10973            // delete 
10974            if (isset($currentBookmark['LessonBookmark']['callan_bookmark_id']) && $currentBookmark['LessonBookmark']['callan_bookmark_id'] == $callan_bookmark_id) {
10975                $response['deleted'] = false;
10976                $response['status'] = false;
10977                return json_encode($response); 
10978            }
10979            
10980            $bookmarkData = array(
10981                'chat_hash' => $chatHash,
10982                'textbook_connect_id' => $textbook_connect_id,
10983                'callan_bookmark_id' => $callan_bookmark_id,
10984                'user_id' => $user_id,
10985                'teacher_id' => $teacher_id,
10986                'type' => $type,
10987                'added_member_type' => 1
10988            );
10989            $this->log("[saveBookmark] latest bookmark: " . json_encode($bookmarkData), "error");
10990
10991            $this->LessonBookmark->clear();
10992            if ($currentBookmark) {
10993                $this->LessonBookmark->read(array_keys($bookmarkData), $currentBookmark['LessonBookmark']['id']);
10994            } else {
10995                // create new record
10996                $this->LessonBookmark->create();
10997            }
10998            $this->LessonBookmark->set($bookmarkData);
10999            if ($this->LessonBookmark->save()){
11000                $response['status'] = true;
11001
11002                // ~ if save status = true, return the bookmark data if exist
11003                if ($response['status'] && isset($checkBookmarkData['CallanBookmarkOption'])) {
11004                    $checkBookmarkData['CallanBookmarkOption']['category_id'] = $category_id;
11005                    $response['data'] = $checkBookmarkData['CallanBookmarkOption'];
11006                }
11007                
11008                // save to lesson bookmark history as well
11009                $this->LessonBookmarkHistory->openDBReplica();
11010                $currentBookmarkHistory = $this->LessonBookmarkHistory->find('first', array(
11011                    'fields' => array(
11012                        'LessonBookmarkHistory.id',
11013                        'LessonBookmarkHistory.callan_bookmark_id'
11014                    ),
11015                    'conditions' => array('user_id' => $user_id, 'chat_hash' => $chatHash),
11016                    'recursive' => -1
11017                ));
11018                $this->LessonBookmarkHistory->closeDBReplica();
11019                
11020                $this->LessonBookmarkHistory->clear();
11021                if ($currentBookmarkHistory) {
11022                    $this->LessonBookmarkHistory->read(array_keys($bookmarkData), $currentBookmarkHistory['LessonBookmarkHistory']['id']);
11023                }
11024                $this->LessonBookmarkHistory->set($bookmarkData);
11025                if (!$this->LessonBookmarkHistory->save()){
11026                    $this->log('[NJ-33115] failed to bookmark history' . json_encode($bookmarkData) , 'error');
11027                }
11028
11029                // update future reservation
11030                $this->LessonSchedulesExtend->updateUpcomingBookmarkedReservation($textbook_connect_id, $user_id);
11031            }
11032        }
11033    
11034        return json_encode($response); 
11035    }
11036
11037    /**
11038     * @api {post} /teacher/api/saveCallanStage saveCallanStage()
11039     * @apiName saveCallanStage
11040     * @apiGroup API
11041     * @apiDescription Save callan stage
11042     * @apiSampleRequest off
11043     * 
11044     * @apiBody {String} chat_hash The chat hash.
11045     * @apiBody {String} status The status.
11046     * @apiBody {String} user_id The user ID.
11047     * @apiBody {String} stageID The stage ID.
11048     * @apiBody {String} textbook_connect_id The textbook connect ID.
11049     * @apiBody {String} type The type.
11050     * 
11051     * @apiSuccess {Boolean} status The status of the request.
11052     * 
11053     * @apiErrorExample {json} Success-Response:
11054     *     {
11055     *       "status": true
11056     *     }
11057     *
11058     * @apiErrorExample {json} Error-Response:
11059     *     {
11060     *       "status": false
11061     *     }
11062     * 
11063     * @apiErrorExample {js} Used in: JS
11064     * Location: "view/HtmlTextBook/index.php"
11065     */
11066    public function saveCallanStage(){
11067        $this->autoRender = false;
11068        $response = array('status' => false);
11069
11070        if($this->request->is('post')){
11071            $chatHash = (isset($this->request->data['chat_hash'])) ? $this->request->data['chat_hash'] : null;
11072            $status = (isset($this->request->data['status'])) ? (int)$this->request->data['status'] : 0;
11073            $user_id = (isset($this->request->data['user_id'])) ? $this->request->data['user_id'] : 0;
11074            $stageID_str = (isset($this->request->data['stageID'])) ? $this->request->data['stageID'] : null;
11075            $textbook_connect_id = (isset($this->request->data['textbook_connect_id'])) ? $this->request->data['textbook_connect_id'] : null;
11076            $type = (isset($this->request->data['type'])) ? $this->request->data['type'] : null;
11077
11078
11079            $stageParts = explode('_', $stageID_str);
11080            $stageID = end($stageParts);
11081
11082            $this->UserNewLessonCallanProgress->openDBReplica();
11083            $stageProgress = $this->UserNewLessonCallanProgress->find('first', array(
11084                'fields' => array(
11085                    'id',
11086                    'status'
11087                ),
11088                'conditions' => array(
11089                    'user_id' => $user_id,
11090                    'callan_stage' => $stageID
11091                ),
11092                'recursive' => -1
11093            ));
11094            $this->UserNewLessonCallanProgress->closeDBReplica();
11095
11096            $progressData = array(
11097                "user_id" => $user_id,
11098                "callan_stage" => $stageID,
11099                "status" => $status
11100            );
11101
11102            if ($status == 1) {
11103                $progressData['completed_date'] = date('Y-m-d H:i:s'); // now
11104                $progressData['completion_chat_hash'] = $chatHash;
11105            } else {
11106                $progressData['completed_date'] = NULL;
11107                $progressData['completion_chat_hash'] = NULL;
11108            }
11109
11110            $this->log("Save Callan Stage: " . json_encode($progressData), "lesson_bookmark");
11111            $this->UserNewLessonCallanProgress->clear();
11112
11113            if ($stageProgress) {
11114                $this->UserNewLessonCallanProgress->read(array_keys($progressData), $stageProgress['UserNewLessonCallanProgress']['id']);
11115            }
11116
11117            $this->UserNewLessonCallanProgress->set($progressData);
11118                if ($this->UserNewLessonCallanProgress->save()){
11119                    $response['status'] = true;
11120                }
11121
11122            if ($status == 1) {
11123                // update lesson bookmark
11124                $this->CallanBookmarkOption->openDBReplica();
11125                $bookmark = $this->CallanBookmarkOption->find('first', array(
11126                    'fields' => array(
11127                        'id'
11128                    ),
11129                    'conditions' => array('stage' => $stageID+1),
11130                    'order' => 'id ASC',
11131                    'recursive' => -1
11132                ));
11133                $this->CallanBookmarkOption->closeDBReplica();
11134
11135                if ($bookmark) {
11136                    $this->LessonBookmark->openDBReplica();
11137                    $currentBookmark = $this->LessonBookmark->find('first', array(
11138                        'fields' => array(
11139                            'id',
11140                            'callan_bookmark_id'
11141                        ),
11142                        'conditions' => array('user_id' => $user_id),
11143                        'recursive' => -1
11144                    ));
11145                    $this->LessonBookmark->closeDBReplica();
11146
11147                    $bookmarkData = array(
11148                        'chat_hash' => $chatHash,
11149                        'textbook_connect_id' => $textbook_connect_id,
11150                        'callan_bookmark_id' => $bookmark['CallanBookmarkOption']['id'],
11151                        'user_id' => $user_id,
11152                        'teacher_id' => $this->Auth->user('id'),
11153                        'type' => $type
11154                    );
11155                    $this->log("Save Bookmark after Stage: " . json_encode($bookmarkData), "lesson_bookmark");
11156        
11157                    $this->LessonBookmark->clear();
11158                    if ($currentBookmark) {
11159                        $this->LessonBookmark->read(array_keys($bookmarkData), $currentBookmark['LessonBookmark']['id']);
11160                    }
11161                    $this->LessonBookmark->set($bookmarkData);
11162                    if ($this->LessonBookmark->save()){
11163                        $response['status'] = true;
11164                    }
11165
11166                    // update future reservation
11167                    $this->LessonSchedulesExtend->updateUpcomingBookmarkedReservation($textbook_connect_id, $user_id);
11168    
11169                }
11170    
11171            }
11172
11173        }
11174    
11175        return json_encode($response); 
11176    }
11177
11178    /**
11179     * @api {post} /teacher/api/fetchBookmark fetchBookmark()
11180     * @apiName fetchBookmark
11181     * @apiGroup API
11182     * @apiDescription Fetch bookmark data
11183     * @apiSampleRequest off
11184     * 
11185     * @apiBody {String} lesson_identifier The lesson identifier.
11186     * @apiBody {String} textbook_connect_id The textbook connect ID.
11187     * 
11188     * @apiSuccess {Boolean} status2 The status of the request.
11189     * 
11190     * @apiErrorExample {json} Success-Response:
11191     *     {
11192     *       "status2": true,
11193     *     }
11194     *
11195      * @apiErrorExample {js} Used in: Ajax
11196     * Location: "view/HtmlTextBook/index.php"
11197     */
11198    public function fetchBookmark() {
11199        $this->autoRender = false;
11200        $response = array('status2' => false);
11201
11202        if($this->request->is('post')){
11203            $lesson_identifier = (isset($this->request->data['lesson_identifier'])) ? $this->request->data['lesson_identifier'] : null;
11204            $textbook_category_id = (isset($this->request->data['textbook_category_id'])) ? $this->request->data['textbook_category_id'] : null;
11205            if (!empty($lesson_identifier) && is_numeric($lesson_identifier)){
11206                $response = $this->LessonBookmark->getBookmarkviaUserId($lesson_identifier, $textbook_category_id);
11207            } else {
11208                $response = $this->LessonBookmark->getBookmarkviaChatHash($lesson_identifier, $textbook_category_id);
11209            }
11210            
11211        }
11212        return json_encode($response);
11213    }
11214
11215    public function fetchBookmarkTextbook() {
11216        $this->autoRender = false;
11217        $bookmarkInfo = array();
11218
11219        if($this->request->is('post')){
11220            $user_id = (isset($this->request->data['user_id'])) ? $this->request->data['user_id'] : null;
11221            $textbook_category_id = (isset($this->request->data['textbook_category_id'])) ? $this->request->data['textbook_category_id'] : null;
11222            
11223            $this->LessonBookmark->openDBReplica();
11224            $bookmark = $this->LessonBookmark->find('first', array(
11225                'fields' => array(
11226                    'Textbook.chapter_id',
11227                    'LessonBookmark.textbook_connect_id',
11228                    'TextbookConnect.category_id',
11229                    'TextbookConnect.subcategory_id',
11230                    'TextbookCategory.type_id',
11231                    'TextbookCategory.textbook_category_type',
11232                ),
11233                'joins' => array(
11234                    array(
11235                        'type' => 'LEFT',
11236                        'alias' => 'TextbookConnect',
11237                        'table' => 'textbook_connects',
11238                        'conditions' => 'TextbookConnect.id = LessonBookmark.textbook_connect_id'
11239                    ),
11240                    array(
11241                        'type' => 'LEFT',
11242                        'alias' => 'TextbookCategory',
11243                        'table' => 'textbook_categories',
11244                        'conditions' => 'TextbookCategory.id = TextbookConnect.category_id'
11245                    ),
11246                    array(
11247                        'type' => 'LEFT',
11248                        'alias' => 'Textbook',
11249                        'table' => 'textbooks',
11250                        'conditions' => 'Textbook.id = TextbookConnect.textbook_id'
11251                    )
11252                ),
11253                'conditions' => array(
11254                    'LessonBookmark.user_id' => $user_id,
11255                    'LessonBookmark.teacher_id' => $this->Auth->user('id'),
11256                    'TextbookConnect.category_id' => $textbook_category_id,
11257                ),
11258                // get most recent bookmark from the series/course
11259                'order' => array('LessonBookmark.modified DESC'),
11260                'recursive' => -1
11261            ));
11262            $this->LessonBookmark->closeDBReplica();
11263
11264            if ($bookmark) {
11265                $bookmarkInfo = array(
11266                    'connect_id' => $bookmark['LessonBookmark']['textbook_connect_id'],
11267                    'chapter_id' => $bookmark['Textbook']['chapter_id'],
11268                    'type_id' => $bookmark['TextbookCategory']['type_id'],
11269                    'category_type' => $bookmark['TextbookCategory']['textbook_category_type'],
11270                    'category_id' => $bookmark['TextbookConnect']['category_id'],
11271                    'subcategory_id' => $bookmark['TextbookConnect']['subcategory_id'],
11272                );
11273            }
11274            
11275        }
11276        return json_encode($bookmarkInfo);
11277    }
11278
11279    /**
11280     * @api {post} /teacher/api/saveNewCallanLevelCheck saveNewCallanLevelCheck()
11281     * @apiName saveNewCallanLevelCheck
11282     * @apiGroup API
11283     * @apiDescription Save new callan level check
11284     * @apiSampleRequest off
11285     * 
11286     * @apiBody {String} chat_hash The chat hash.
11287     * @apiBody {String} stage The stage.
11288     * 
11289     * @apiSuccess {Boolean} status The status of the request.
11290     * @apiSuccess {Array} data The data. (`$this->CallanBookmarkOption->find`)
11291     * 
11292     * @apiErrorExample {json} Success-Response:
11293     *     {
11294     *       "status": true,
11295     *       "data": {...}
11296     *     }
11297     *
11298     * @apiErrorExample {json} Error-Response:
11299     *     {
11300     *       "status": false,
11301     *       "error": {
11302     *           "message": "Cannot locate Callan Lesson Data"
11303     *       }
11304     *     }
11305     * 
11306      * @apiErrorExample {js} Used in: Ajax
11307     * Location: "view/HtmlTextBook/index.php"
11308     */
11309    public function saveNewCallanLevelCheck(){
11310        $this->autoRender = false;
11311        $response = array('status' => false);
11312
11313        if($this->request->is('post')){
11314            $chatHash = (isset($this->request->data['chat_hash'])) ? $this->request->data['chat_hash'] : null;
11315            $stage = (isset($this->request->data['stage'])) ? $this->request->data['stage'] : null;
11316            $category_id = isset($this->request->data['category_id']) ? $this->request->data['category_id'] : null;
11317            
11318            // get student and teacher IDs
11319            $this->LessonOnair->openDBReplica();
11320            $onairData = $this->LessonOnair->find('first', array(
11321                'fields' => array(
11322                    'LessonOnair.user_id',
11323                    'LessonOnair.teacher_id',
11324                    'LessonOnair.connect_id',
11325                ),
11326                'conditions' => array('chat_hash' => $chatHash),
11327                'recursive' => -1
11328            ));
11329            $this->LessonOnair->closeDBReplica();
11330
11331            // validate callan lesson
11332            // TODO: add logic for outside lesson
11333            if(empty($onairData)){
11334
11335                $this->LessonOnairsLog->openDBReplica();
11336                $onairLogData = $this->LessonOnairsLog->find('first', array(
11337                    'fields' => array(
11338                        'LessonOnairsLog.user_id',
11339                        'LessonOnairsLog.teacher_id',
11340                        'LessonOnairsLog.end_time',
11341                        'LessonOnairsLog.connect_id'
11342                    ),
11343                    'conditions' => array('chat_hash' => $chatHash),
11344                    'recursive' => -1
11345                ));
11346                $this->LessonOnairsLog->closeDBReplica();
11347
11348                if(empty($onairLogData)){
11349                    $response['error']['message'] = "Cannot locate Callan Lesson Data";
11350                    return json_encode($response); 
11351                }
11352
11353                // if greater than 3 minutes after lesson end
11354                $endTimeTimestamp = strtotime($onairLogData['LessonOnairsLog']['end_time']);
11355                $check3MinAfteLesson = (strtotime('now') <= $endTimeTimestamp + (3 * 60)) ? true : false; 
11356                if (!$check3MinAfteLesson) {
11357                    $response['error']['message'] = "Cannot bookmark after 3 minutes of lesson end";
11358                    return json_encode($response); 
11359                }
11360
11361                $user_id = $onairLogData['LessonOnairsLog']['user_id'];
11362                $connect_id = $onairLogData['LessonOnairsLog']['connect_id'] ?? null;
11363            } else {
11364                $user_id = $onairData['LessonOnair']['user_id'];
11365                $connect_id = $onairData['LessonOnair']['connect_id'] ?? null;
11366            }
11367
11368            if(!isset($connect_id)){
11369                $response['error']['message'] = "connect_id is empty";
11370                return json_encode($response); 
11371            }
11372
11373
11374            $saveParams = array(
11375                'stageID' =>  "Stage 0",
11376                'user_id' => $user_id,
11377                'level_check_stage' => $stage
11378            );
11379
11380            $this->UserNewLessonCallanProgress->saveStartDate($saveParams);
11381            
11382            $this->User->clear();
11383            $this->User->read(array('new_callan_level_check'), $user_id);
11384            $this->User->validate = false;
11385            $this->User->set(array('new_callan_level_check' =>  Configure::read('callan_entry_level.finished')));
11386            if($this->User->save()){
11387                $response['status'] = true;
11388            }
11389
11390            // update lesson bookmark
11391            $this->CallanBookmarkOption->openDBReplica();
11392            $bookmark = $this->CallanBookmarkOption->find('first', array(
11393                'fields' => array(
11394                    'id',
11395                    'stage',
11396                    'page',
11397                    'type',
11398                    'headword',
11399                    'paragraph',
11400                ),
11401                'conditions' => array('stage' => $stage),
11402                'order' => 'id ASC',
11403                'recursive' => -1
11404            ));
11405            $this->CallanBookmarkOption->closeDBReplica();
11406            
11407            if(isset($bookmark['CallanBookmarkOption']['stage'])){
11408                $this->CallanBookmarkOption->openDBReplica();
11409                $getTypeFormat = $this->CallanBookmarkOption->find('first', array(
11410                    'fields' => array(
11411                        "page", "type", "GROUP_CONCAT(paragraph ORDER BY paragraph SEPARATOR '|')  as paragraph"
11412                    ),
11413                    'conditions' => array(
11414                        'stage' => $bookmark['CallanBookmarkOption']['stage'],
11415                        'page' => $bookmark['CallanBookmarkOption']['page']
11416                    ),
11417                    'group' => 'page',
11418                    'recursive' => -1
11419                ));
11420                $this->CallanBookmarkOption->closeDBReplica();
11421            }
11422            
11423            // NJ-33801 - Change Type Format after bookmark
11424            $paragraphsInfo = !empty($getTypeFormat) ? explode('|', $getTypeFormat[0]['paragraph']) : null;
11425            
11426            if(!empty($paragraphsInfo)){
11427                $paragraphsInfo = explode('|', $getTypeFormat[0]['paragraph']);                
11428                $first = $paragraphsInfo[0];
11429                $last = end($paragraphsInfo);
11430                
11431                preg_match('/Lesson\s*(\d+)\s*,\s*(?:pp\s*(\d+)|(\d+))/i', $first, $firstMatch);
11432                preg_match('/Lesson\s*(\d+)\s*,\s*(?:pp\s*(\d+)|(\d+))/i', $last, $lastMatch);
11433                
11434                $lessonStart = $firstMatch[1];
11435                $lessonEnd = $lastMatch[1];
11436                $pageStart = $firstMatch[2];
11437                $pageEnd = $lastMatch[2];
11438                
11439                $lessonRange = ($lessonStart === $lessonEnd) ? "Lesson $lessonStart" : "Lesson $lessonStart~$lessonEnd";
11440                
11441                $pageRange = ($pageStart === $pageEnd) ? "pp$pageStart" : "pp$pageStart~$pageEnd";
11442            
11443                $formattedString = "".$bookmark['CallanBookmarkOption']['type']." ($lessonRange$pageRange)";
11444                
11445                $bookmark['CallanBookmarkOption']['formattedtype'] = $formattedString;    
11446            }else{
11447                $response['error']['message'] = "Paragraph Information is empty";
11448                return json_encode($response);
11449            }
11450
11451            // ~ if save status = true, return the bookmark data if exist
11452            if ($response['status'] && isset($bookmark['CallanBookmarkOption'])) {
11453                $bookmark['CallanBookmarkOption']['category_id'] = $category_id;
11454                $response['data'] = $bookmark['CallanBookmarkOption'];
11455            }
11456
11457            $this->LessonBookmark->openDBReplica();
11458            $currentBookmark = $this->LessonBookmark->find('first', array(
11459                'fields' => array(
11460                    'id',
11461                    'callan_bookmark_id'
11462                ),
11463                'conditions' => array('user_id' => $user_id),
11464                'recursive' => -1
11465            ));
11466            $this->LessonBookmark->closeDBReplica();
11467
11468            // Temporary value for Stage 3-12
11469            // todo: update after Stage 3-12 release
11470            // $bookmark_id = 0;
11471
11472            // if ($bookmark) {
11473            $bookmark_id = $bookmark['CallanBookmarkOption']['id'];
11474            // }
11475            
11476            $new_callan_category_ids = Configure::read('new_callan_textbook_category_id');
11477            $seres_textbook_connect_id = $this->getStageConnectID($stage, $new_callan_category_ids[0]);
11478            $course_textbook_connect_id = $this->getStageConnectID($stage, $new_callan_category_ids[1]);
11479            
11480            $bookmarkData = [];
11481                
11482            if (!empty($seres_textbook_connect_id)) {
11483                $bookmarkData[] = array(
11484                    'chat_hash' => $chatHash,
11485                    'textbook_connect_id' => $seres_textbook_connect_id,
11486                    'callan_bookmark_id' => $bookmark_id,
11487                    'user_id' => $user_id,
11488                    'teacher_id' => $this->Auth->user('id'),
11489                    'type' => 1 // callan
11490                );
11491            }
11492            
11493            if (!empty($course_textbook_connect_id)) {
11494                $bookmarkData[] = array(
11495                    'chat_hash' => $chatHash,
11496                    'textbook_connect_id' => $course_textbook_connect_id,
11497                    'callan_bookmark_id' => $bookmark_id,
11498                    'user_id' => $user_id,
11499                    'teacher_id' => $this->Auth->user('id'),
11500                    'type' => 1 // callan
11501                );
11502            }
11503
11504            $this->LessonBookmark->clear();
11505            if ($currentBookmark) {
11506                $this->LessonBookmark->query("Update lesson_bookmarks set callan_bookmark_id = $bookmark_id, textbook_connect_id = $seres_textbook_connect_id where user_id = $user_id and textbook_connect_id IN (SELECT id FROM textbook_connects where category_id = $new_callan_category_ids[0])");
11507                $this->LessonBookmark->query("Update lesson_bookmarks set callan_bookmark_id = $bookmark_id, textbook_connect_id = $course_textbook_connect_id where user_id = $user_id and textbook_connect_id IN (SELECT id FROM textbook_connects where category_id = $new_callan_category_ids[1])");
11508            } else if (!empty($bookmarkData)){
11509                try {
11510                    $this->LessonBookmark->create();
11511                    $this->LessonBookmark->saveAll($bookmarkData);
11512                } catch (Exception $e) {
11513                    $this->log("failed to save new bookmark-> " . json_encode($bookmarkData), "debug");
11514                }
11515            }
11516
11517            //if level Check test save the stage number
11518            $saveData = array(
11519                'chat_hash' => $chatHash,
11520                'textbook_connect_id' => $connect_id,
11521                'callan_bookmark_id' => $bookmark_id,
11522                'user_id' => $user_id,
11523                'teacher_id' => $this->Auth->user('id'),
11524                'type' => 1 // callan
11525            );
11526
11527            // save to lesson bookmark history as well
11528            $this->LessonBookmarkHistory->openDBReplica();
11529            $currentBookmarkHistory = $this->LessonBookmarkHistory->find('first', array(
11530                'fields' => array(
11531                    'id',
11532                    'callan_bookmark_id'
11533                ),
11534                'conditions' => array('user_id' => $user_id, 'chat_hash' => $chatHash),
11535                'recursive' => -1
11536            ));
11537            $this->LessonBookmarkHistory->closeDBReplica();
11538            
11539            $this->LessonBookmarkHistory->clear();
11540            if ($currentBookmarkHistory) {
11541                $this->LessonBookmarkHistory->read(array_keys($saveData), $currentBookmarkHistory['LessonBookmarkHistory']['id']);
11542            }
11543            $this->LessonBookmarkHistory->set($saveData);
11544            if (!$this->LessonBookmarkHistory->save()){
11545                $this->log('[NJ-33115] failed to bookmark history' . json_encode($saveData) , 'debug');
11546            }
11547            
11548            
11549        }
11550    
11551        return json_encode($response); 
11552    }
11553
11554    private function getStageConnectID($stage, $category_id = null){
11555        $conditions = array(
11556            'Textbook.name LIKE' => "%Stage ".$stage."%"
11557        );
11558        
11559        if (!empty($category_id)) {
11560            $conditions['TextbookConnect.category_id'] = $category_id;
11561        }
11562        
11563        
11564        $this->loadModel("Textbook");
11565        $this->Textbook->openDBReplica();
11566        $textbookData = $this->Textbook->find('first', array(
11567            'fields' => array(
11568                'TextbookConnect.id'
11569            ),
11570            'joins' => array(
11571                array(
11572                    'table' => 'textbook_connects',
11573                    'alias' => 'TextbookConnect',
11574                    'type' => 'INNER',
11575                    'conditions' => array(
11576                        'TextbookConnect.textbook_id = Textbook.id'
11577                    )
11578                )
11579            ),
11580            'conditions' => $conditions,
11581            'recursive' => -1
11582        ));
11583        $this->Textbook->closeDBReplica();
11584
11585        if (!empty($textbookData)) {
11586            return $textbookData['TextbookConnect']['id'];
11587        }
11588
11589        return 0;
11590    }
11591
11592    /**
11593     * @api {post} /teacher/api/recordPlayedPhrase recordPlayedPhrase()
11594     * @apiName recordPlayedPhrase
11595     * @apiGroup API
11596     * @apiDescription Record played phrase
11597     * @apiSampleRequest off
11598     * 
11599     * @apiBody {String} avatar_phase_id The avatar phrase ID.
11600     * 
11601     * @apiSuccess {Boolean} success The status of the request.
11602     * 
11603     * @apiErrorExample {json} Success-Response:
11604     *     {
11605     *       "success": true
11606     *     }
11607     *
11608      * @apiErrorExample {js} Used in: Ajax
11609     * Location: "view/Home/index.php"
11610     */
11611    public function recordPlayedPhrase(){
11612        $this->autoRender = false;
11613        $teacherId = $this->Auth->user('id');
11614        $avatarPhraseId = $this->request->data['avatar_phase_id'] ?? null;
11615        $onAirModel = 'LessonOnair';
11616        $chatHash = $this->getCurrentChatHash($onAirModel, $teacherId);
11617        $this->Session->write('chatHash', $chatHash);
11618        $this->loadModel('AvatarPhraseRecord');
11619    
11620        if (!$teacherId || !$avatarPhraseId) {
11621            return json_encode(['success' => false]);
11622        }
11623
11624        // check existing record
11625        $this->AvatarPhraseRecord->openDBReplica();
11626        $existRecord = $this->AvatarPhraseRecord->find('first', [
11627            'fields' => ['AvatarPhraseRecord.id'],
11628            'conditions' => [
11629                'AvatarPhraseRecord.teacher_id' => $teacherId,
11630                'AvatarPhraseRecord.avatar_phrase_id' => $avatarPhraseId,
11631                'AvatarPhraseRecord.chat_hash' => $chatHash
11632            ],
11633        ]);
11634        $this->AvatarPhraseRecord->closeDBReplica();
11635        
11636        if ($existRecord) {
11637            $this->AvatarPhraseRecord->query("UPDATE avatar_phrase_records SET total = total + 1 WHERE id = {$existRecord['AvatarPhraseRecord']['id']}");
11638            return json_encode(['success' => true]);
11639        } else {
11640            // insert new record
11641            $this->AvatarPhraseRecord->create();
11642            $this->AvatarPhraseRecord->set([
11643                'teacher_id' => $teacherId,
11644                'avatar_phrase_id' => $avatarPhraseId,
11645                'total' => 1,
11646                'chat_hash' => $chatHash
11647            ]);
11648            return json_encode(['success' => $this->AvatarPhraseRecord->save() ? true : false]);
11649        }
11650    }
11651
11652    /**
11653     * @api {post} /teacher/api/gameGetPlayersProfile gameGetPlayersProfile()
11654     * @apiName gameGetPlayersProfile
11655     * @apiGroup API
11656     * @apiDescription Get players profile
11657     * @apiSampleRequest off
11658     * 
11659     * @apiBody {String} user_id The user ID.
11660     * @apiBody {String} teacher_id The teacher ID.
11661     * 
11662     * @apiSuccess {Boolean} error The status of the request.
11663     * @apiSuccess {Array} profile_data The profile data.
11664     * @apiSuccess {Array} profile_data.user The user profile data.
11665     * @apiSuccess {Array} profile_data.teacher The teacher profile data.
11666     * @apiSuccess {Number} profile_data.user.id The user ID.
11667     * @apiSuccess {String} profile_data.user.profile_img The user profile image URL.
11668     * @apiSuccess {Number} profile_data.teacher.id The teacher ID.
11669     * @apiSuccess {String} profile_data.teacher.profile_img The teacher profile image URL.
11670     * 
11671     * @apiErrorExample {json} Success-Response:
11672     *     {
11673     *       "error": false,
11674     *       "profile_data": {
11675     *           "user": {
11676     *               "id": 1,
11677     *               "profile_img": "url"
11678     *           },
11679     *           "teacher": {
11680     *               "id": 1,
11681     *               "profile_img": "url"
11682     *           }
11683     *       }
11684     *     }
11685     *
11686     * @apiErrorExample {json} Error-Response:
11687     *     {
11688     *       "error": true,
11689     *       "profile_data": [],
11690     *       "err_msg": "Invalid params"
11691     *     }
11692     * 
11693     * @apiErrorExample {js} Used in: Elements
11694     * Location: "view/Elements/chat_area_js.php"
11695     */
11696    public function gameGetPlayersProfile(){
11697        $this->autoRender = false;
11698        $res = [
11699            'error' => true,
11700            'profile_data' => []
11701        ];
11702        # check if the request is pot
11703        if($this->request->is('post')){
11704            $data = $this->request->data;
11705            $user_id = $data['user_id'] ?? null;
11706            $teacher_id = $data['teacher_id'] ?? null;
11707
11708            if(!$user_id || !$teacher_id){
11709                $res['err_msg'] = 'Invalid params';
11710                return json_encode($res);
11711            }
11712
11713            $user_data = $this->User->getUserData(
11714                ['User.id' => $user_id], // - condition
11715                ['*'], // - fields
11716                'first' // - type
11717            );
11718
11719            $tacher_data = $this->Teacher->getTeacherInfo($teacher_id);
11720
11721            if(!$user_data || !$tacher_data){
11722                $res['err_msg'] = 'Invalid params';
11723                return json_encode($res);
11724            }
11725
11726            $user_obj = new UserTable($user_data['User']);
11727            $teacher_obj = new TeacherTable($tacher_data['Teacher']);
11728
11729            $res['profile_data'] = [
11730                'user' => [
11731                    'id' => (int)$user_obj->id, 
11732                    'profile_img' => $user_obj->getImageUrl()
11733                ],
11734                'teacher' => [
11735                    'id' => (int)$teacher_obj->id, 
11736                    'profile_img' => $teacher_obj->getImageUrl()
11737                ],
11738            ];
11739
11740            $res['error'] = false;
11741
11742        }
11743        return json_encode($res);
11744    }
11745
11746    /**
11747     * @api {post} /teacher/api/saveGameResult saveGameResult()
11748     * @apiName saveGameResult
11749     * @apiGroup API
11750     * @apiDescription Save game result
11751     * @apiSampleRequest off
11752     * 
11753     * @apiBody {String} user_id The user ID.
11754     * @apiBody {String} teacher_id The teacher ID.
11755     * @apiBody {String} teacher_score The teacher score.
11756     * @apiBody {String} student_score The student score.
11757     * @apiBody {String} winner The winner.
11758     * @apiBody {String} end_time The end time.
11759     * @apiBody {String} textbook_id The textbook ID.
11760     * @apiBody {String} chat_hash The chat hash.
11761     * 
11762     * @apiSuccess {Boolean} error The status of the request.
11763     * @apiSuccess {Array} game_result The game result data.
11764     * @apiSuccess {Number} game_result.user_id The user ID.
11765     * @apiSuccess {Number} game_result.teacher_id The teacher ID.
11766     * @apiSuccess {Number} game_result.student_score The student score.
11767     * @apiSuccess {Number} game_result.teacher_score The teacher score.
11768     * @apiSuccess {Number} game_result.end_time The end time.
11769     * @apiSuccess {Number} game_result.textbook_id The textbook ID.
11770     * @apiSuccess {Number} game_result.teacher_result The teacher result.
11771     * 
11772     * @apiErrorExample {json} Success-Response:
11773     *     {
11774     *       "error": false,
11775     *       "game_result": {
11776     *              "user_id": 1,
11777     *              "teacher_id": 1,
11778     *              "student_score": 1,
11779     *              "teacher_score": 1,
11780     *              "end_time": "2021-09-01 00:00:00",
11781     *              "textbook_id": 1,
11782     *              "teacher_result": 1
11783     *              }
11784     *     }
11785     *
11786     * @apiErrorExample {json} Error-Response:
11787     *     {
11788     *       "error": true,
11789     *       "game_result": []
11790     *     }
11791     */
11792    public function saveGameResult(){
11793        $this->autoRender = false;
11794        $res = [
11795            'error' => true,
11796            'game_result' => []
11797        ];
11798        # check if the request is post
11799        if($this->request->is('post')){
11800            $data = $this->request->data;
11801            $user_id = $data['user_id'] ?? null;
11802            $teacher_id = $data['teacher_id'] ?? null;
11803            $teacher_score = $data['teacher_score'] ?? 0;
11804            $student_score = $data['student_score'] ?? 0;
11805            $winner = $data['winner'] ?? 0;
11806            $end_time = $data['end_time'] ?? null;
11807            $textbook_id = $data['textbook_id'] ?? null;
11808            $chatHash = $data['chat_hash'] ?? null;
11809
11810            $game_result_data = [
11811                'user_id' => $user_id,
11812                'teacher_id' => $teacher_id,
11813                'student_score' => $student_score,
11814                'teacher_score' => $teacher_score,
11815                'end_time' => $end_time,
11816                'textbook_id' => $textbook_id,
11817            ];
11818
11819            // - draw
11820            if($winner == 0){
11821                $game_result_data['teacher_result'] = Configure::read('game_results.draw');
11822                $game_result_data['student_result'] = Configure::read('game_results.draw');
11823            
11824            // - student wins
11825            } elseif ($winner == 1) {
11826                $game_result_data['student_result'] = Configure::read('game_results.win');
11827                $game_result_data['teacher_result'] = Configure::read('game_results.loss');
11828            
11829            // - teacher wins
11830            } elseif ($winner == 2) {
11831                $game_result_data['teacher_result'] = Configure::read('game_results.win');
11832                $game_result_data['student_result'] = Configure::read('game_results.loss');
11833            }
11834
11835            // - save game result
11836            $gs_model = ClassRegistry::init('GameResult');
11837            $gs_model->clear();
11838            $gs_model->set($game_result_data);
11839            if(!$gs_model->save()){
11840                $res['msg'] = 'Error saving game result';
11841            } else {
11842                $res['error'] = false;
11843
11844                if (!class_exists('myMemcached')) {
11845                    App::uses('myMemcached', 'Lib');
11846                }
11847                $game_mem = new myMemcached();
11848                // delete memcache game
11849                if($game_mem->get($chatHash)){
11850                    $game_mem->delete($chatHash);
11851                }
11852            }
11853            $res['game_result'] = $game_result_data;
11854        }
11855        return json_encode($res);
11856    }
11857
11858    /**
11859     * @api {post} /teacher/api/saveGameDataMemcache saveGameDataMemcache()
11860     * @apiName saveGameDataMemcache
11861     * @apiGroup API
11862     * @apiDescription Save game data to memcache
11863     * @apiSampleRequest off
11864     * 
11865     * @apiSuccess {String} message Success message
11866     * @apiSuccess {Object} data Data returned from the API
11867     * @apiSuccess {Number} data.id Unique identifier of the resource
11868     * @apiSuccess {String} data.name Name of the resource
11869     * @apiSuccess {String} data.description Description of the resource
11870     * @apiSuccess {String} data.created_at Timestamp when the resource was created
11871     * @apiSuccess {String} data.updated_at Timestamp when the resource was last updated
11872     * 
11873     * 
11874     * @apiErrorExample {json} Success-Response:
11875     *     {
11876     *       "error": false,
11877     *       "game_data": {
11878     *              "start": 1,
11879     *              "first_turn": 1,
11880     *              "cardSetNo": 1,
11881     *              "player_turn": 1,
11882     *              "scores": {
11883     *                  "student_score": 1,
11884     *                  "teacher_score": 1
11885     *              },
11886     *              "cards": [
11887     *                  {
11888     *                      "card_no": 1,
11889     *                      "animal": "animal",
11890     *                      "flipped": 1,
11891     *                      "show": 1,
11892     *                      "match_card_no": 1
11893     * 
11894     *       }
11895     *     }
11896     *
11897     * @apiErrorExample {json} Error-Response:
11898     *     {
11899     *       "error": true,
11900     *       "game_data": []
11901     *     }
11902     * 
11903     * @apiErrorExample {js} Used in: Elements
11904     * Location: "view/Elements/chat_area_js.php"
11905     */
11906    public function saveGameDataMemcache(){
11907        $this->autoRender = false;
11908        $res = [
11909            'error' => true,
11910            'game_data' => []
11911        ];
11912
11913        # check if the request is pot
11914        if($this->request->is('post')){
11915            $data = $this->request->data;
11916            $res['params'] = $data;
11917            $chatHash = $data['chat_hash'] ?? null;
11918            $action = $data['action'] ?? null;
11919            $params = $data['params'] ?? null;
11920            $teacherID = $data['teacher_id'] ?? null;
11921            $userID = $data['user_id'] ?? null;
11922
11923            if(!$chatHash){ return $res; }
11924
11925            // - use memcached
11926            if (!class_exists('myMemcached')) {
11927                App::uses('myMemcached', 'Lib');
11928            }
11929            $game_mem = new myMemcached();
11930            $game_mem_key = $chatHash;
11931            $game_cache = $game_mem->get($game_mem_key);
11932            $game_data = $game_cache ?? [];
11933            switch ($action) {
11934                case 'start':
11935                    $game_data['start'] = (int)1;
11936                    break;
11937
11938                case 'first_turn':
11939                    $game_data['first_turn'] = $params['player_turn'] = $params['first_turn'] ?? null;
11940                    break;
11941                
11942                case 'card_set':
11943                    $game_data['cardSetNo'] = (int)$params['cardSetNo'] ?? 0;
11944                    break;
11945
11946                case 'player_turn':
11947                    $game_data['player_turn'] = (int)$params['player_turn'] ?? 0;
11948                    break;
11949                
11950                case 'scores':
11951                    $game_data['scores'] = [
11952                        'student_score' => (int)$params['student_score'] ?? 0,
11953                        'teacher_score' => (int)$params['teacher_score'] ?? 0,
11954                    ];
11955                    break;
11956
11957                case 'animal_cards':
11958                    $game_data['cards'] = $params['animal_cards'] ?? [];
11959                    break;
11960
11961                case 'cards_match':
11962                    if(
11963                        !empty($params['selected_cards']) 
11964                        && isset($params['is_match'])
11965                    ){
11966                        $selected_cards = $params['selected_cards'];
11967                        $selected_card1 = $selected_cards['selected_card_1'];
11968                        $selected_card2 = $selected_cards['selected_card_2'];
11969                        $card_selected_idx1 = myTools::getArrayIndexBySearchingColumnValue($game_data['cards'], 'card_no', $selected_card1);
11970                        $card_selected_idx2 = myTools::getArrayIndexBySearchingColumnValue($game_data['cards'], 'card_no', $selected_card2);
11971
11972                        if($params['is_match'] == 1){
11973                            $game_data['cards'][$card_selected_idx1]['show'] =  0;
11974                            $game_data['cards'][$card_selected_idx1]['match_card_no'] =  $selected_card2;
11975                            $game_data['cards'][$card_selected_idx2]['show'] =  0;
11976                            $game_data['cards'][$card_selected_idx2]['match_card_no'] =  $selected_card1;
11977
11978                        } else {
11979                            $game_data['cards'][$card_selected_idx1]['flipped'] = 0;
11980                            $game_data['cards'][$card_selected_idx2]['flipped'] = 0;
11981                            
11982                        }
11983                        
11984                        $game_data['player_moves'] = 0;
11985                        $game_data['selected_card1_id'] = 0;
11986                        $game_data['selected_card2_id'] = 0;
11987                        $game_data['selected_card1'] = null;
11988                        $game_data['selected_card2'] = null;
11989                    }
11990                    
11991                    break;
11992                    
11993                case 'flip_card':
11994                    $card_number_flip = $params['card_no'] ?? 0;
11995                    $player_moves = (int)$params['player_moves'] ?? 0;
11996                    
11997                    if(!$card_number_flip){ break; }
11998
11999                    $cards_data = $game_data['cards'] ?? [];
12000                    if($cards_data){
12001                        $card_idx = myTools::getArrayIndexBySearchingColumnValue($cards_data, 'card_no', $card_number_flip);
12002                        $cards_data[$card_idx]['flipped'] = 1;
12003                        $cards_data[$card_idx]['match_card_no'] = 0;
12004                    }
12005
12006                    if($player_moves == 1){
12007                        $game_data['selected_card1_id'] = (int)$card_number_flip;
12008                        $game_data['selected_card1'] = $cards_data[$card_idx]['animal'];
12009                    } elseif($player_moves == 2){
12010                        $game_data['selected_card2_id'] = (int)$card_number_flip;
12011                        $game_data['selected_card2'] = $cards_data[$card_idx]['animal'];
12012                    }
12013                    
12014                    $game_data['player_moves'] = $player_moves;
12015                    $game_data['cards'] = $cards_data;
12016                    break;
12017
12018                case 'players_profile':
12019                    $game_data['players_profile'] = $params['players_profile'];
12020                    $game_data['chat_hash'] = $params['chat_hash'] ?? null;
12021                    $game_data['start_time'] = $params['start_time'] ?? null;
12022                    $game_data['teacher_id'] = $params['teacher_id'] ?? null;
12023                    $game_data['user_id'] = $params['user_id'] ?? null;
12024
12025                    break;
12026            }
12027
12028            // - update game cache data
12029            if($game_cache){
12030                $game_mem->set(array(
12031                    'key' => $game_mem_key,
12032                    'value' => $game_data,
12033                ));
12034
12035            //- create game cache data
12036            } else {
12037                $game_mem->set(array(
12038                    'key' => $game_mem_key,
12039                    'value' => $game_data,
12040                    'expire' => 1800
12041                ));
12042            }
12043
12044            $res['error'] = false;
12045            $res['game_data'] = $game_data;
12046            return json_encode($res);
12047        }
12048
12049    }
12050
12051    /**
12052     * @api {post} /teacher/api/gameDataMemcache gameDataMemcache()
12053     * @apiName gameDataMemcache
12054     * @apiGroup API
12055     * @apiDescription Get game data from memcache
12056     * @apiSampleRequest off
12057     * 
12058     * @apiBody {String} chat_hash The chat hash.
12059     * @apiBody {String} action The action to be performed (e.g., start, first_turn, card_set, player_turn, scores, animal_cards, cards_match, flip_card, players_profile).
12060      * 
12061     * @apiSuccess {Boolean} error Indicates if there was an error.
12062      * @apiSuccess {Object} game_data The game data stored in Memcache.
12063      * @apiSuccess {Object} game_data.start Indicates if the game has started.
12064      * @apiSuccess {Number} game_data.first_turn The first turn player.
12065      * @apiSuccess {Number} game_data.cardSetNo The card set number.
12066      * @apiSuccess {Number} game_data.player_turn The current player's turn.
12067      * @apiSuccess {Object} game_data.scores The scores of the players.
12068      * @apiSuccess {Number} game_data.scores.student_score The student's score.
12069      * @apiSuccess {Number} game_data.scores.teacher_score The teacher's score.
12070      * @apiSuccess {Object[]} game_data.cards The list of animal cards.
12071      * @apiSuccess {Number} game_data.cards.card_no The card number.
12072      * @apiSuccess {String} game_data.cards.animal The animal on the card.
12073      * @apiSuccess {Number} game_data.cards.flipped Indicates if the card is flipped.
12074      * @apiSuccess {Number} game_data.cards.match_card_no The matching card number.
12075      * @apiSuccess {Number} game_data.player_moves The number of player moves.
12076      * @apiSuccess {Number} game_data.selected_card1_id The ID of the first selected card.
12077      * @apiSuccess {String} game_data.selected_card1 The animal on the first selected card.
12078      * @apiSuccess {Number} game_data.selected_card2_id The ID of the second selected card.
12079      * @apiSuccess {String} game_data.selected_card2 The animal on the second selected card.
12080      * @apiSuccess {Object} game_data.players_profile The profiles of the players.
12081      * @apiSuccess {String} game_data.players_profile.teacher The teacher's name.
12082      * @apiSuccess {String} game_data.players_profile.student The student's name.
12083      * @apiSuccess {String} game_data.chat_hash The chat hash.
12084      * @apiSuccess {String} game_data.start_time The start time of the game.
12085      * @apiSuccess {Number} game_data.teacher_id The ID of the teacher.
12086      * @apiSuccess {Number} game_data.user_id The ID of the user.
12087      * @apiSuccess {Object} params The parameters sent in the request. 
12088      * @apiSuccess {String} params.chat_hash The chat hash.
12089      * @apiSuccess {String} params.action The action performed.
12090      * @apiSuccess {Object} params.params The parameters for the action.
12091      * @apiSuccess {Number} params.params.player_turn The current player's turn.
12092      * @apiSuccess {Number} params.params.first_turn The first turn player.
12093      * @apiSuccess {Number} params.params.cardSetNo The card set number.
12094      * @apiSuccess {Number} params.params.student_score The student's score.
12095      * @apiSuccess {Number} params.params.teacher_score The teacher's score.
12096      * @apiSuccess {Object[]} params.params.animal_cards The list of animal cards.
12097      * @apiSuccess {Number} params.params.animal_cards.card_no The card number.
12098      * @apiSuccess {String} params.params.animal_cards.animal The animal on the card.
12099      * @apiSuccess {Object} params.params.selected_cards The selected cards.
12100      * @apiSuccess {Number} params.params.selected_cards.selected_card_1 The first selected card number.
12101      * @apiSuccess {Number} params.params.selected_cards.selected_card_2 The second selected card number.
12102      * @apiSuccess {Number} params.params.is_match Indicates if the selected cards match.
12103      * @apiSuccess {Number} params.params.card_no The card number.
12104      * @apiSuccess {Number} params.params.player_moves The number of player moves.
12105      * @apiSuccess {Object} params.params.players_profile The profiles of the players.
12106      * @apiSuccess {String} params.params.players_profile.teacher The teacher's name.
12107      * @apiSuccess {String} params.params.players_profile.student The student's name.
12108      * @apiSuccess {String} params.params.chat_hash The chat hash.
12109      * @apiSuccess {String} params.params.start_time The start time of the game.
12110      * @apiSuccess {Number} params.params.teacher_id The ID of the teacher.
12111      * @apiSuccess {Number} params.params.user_id The ID of the user.
12112     * 
12113     * @apiErrorExample {json} Success-Response:
12114     * {
12115     *   "error": false,
12116     *   "game_data": {
12117     *     "start": 1,
12118     *     "first_turn": 1,
12119     *     "cardSetNo": 1,
12120     *     "player_turn": 1,
12121     *     "scores": {
12122     *       "student_score": 10,
12123     *       "teacher_score": 5
12124     *     },
12125     *     "cards": [
12126     *       {
12127     *         "card_no": 1,
12128     *         "animal": "Lion",
12129     *         "flipped": 1,
12130     *         "match_card_no": 0
12131     *       }
12132     *     ],
12133     *     "player_moves": 2,
12134     *     "selected_card1_id": 1,
12135     *     "selected_card1": "Lion",
12136     *     "selected_card2_id": 2,
12137     *     "selected_card2": "Tiger",
12138     *     "players_profile": {
12139     *       "teacher": "TeacherName",
12140     *       "student": "StudentName"
12141     *     },
12142     *     "chat_hash": "abc123",
12143     *     "start_time": "2023-10-01 10:00:00",
12144     *     "teacher_id": 123,
12145     *     "user_id": 456
12146     *   },
12147     *   "params": {
12148     *     "chat_hash": "abc123",
12149     *     "action": "start",
12150     *     "params": {
12151     *       "player_turn": 1,
12152     *       "first_turn": 1,
12153     *       "cardSetNo": 1,
12154     *       "student_score": 10,
12155     *       "teacher_score": 5,
12156     *       "animal_cards": [
12157     *         {
12158     *           "card_no": 1,
12159     *           "animal": "Lion"
12160     *         }
12161     *       ],
12162     *       "selected_cards": {
12163     *         "selected_card_1": 1,
12164     *         "selected_card_2": 2
12165     *       },
12166     *       "is_match": 1,
12167     *       "card_no": 1,
12168     *       "player_moves": 2,
12169     *       "players_profile": {
12170     *         "teacher": "TeacherName",
12171     *         "student": "StudentName"
12172     *       },
12173     *       "chat_hash": "abc123",
12174     *       "start_time": "2023-10-01 10:00:00",
12175     *       "teacher_id": 123,
12176     *       "user_id": 456
12177     *     }
12178     *   }
12179     * }
12180     *
12181      * @apiError {Boolean} error Indicates if there was an error.
12182      * @apiError {Object} game_data The game data stored in Memcache.
12183      * @apiError {Object} params The parameters sent in the request.
12184      *
12185      * @apiErrorExample {json} Error-Response:
12186      * {
12187      *   "error": true,
12188      *   "game_data": [],
12189      *   "params": {
12190      *     "chat_hash": "abc123",
12191      *     "action": "start",
12192      *     "params": {
12193      *       "player_turn": 1,
12194      *       "first_turn": 1,
12195      *       "cardSetNo": 1,
12196      *       "student_score": 10,
12197      *       "teacher_score": 5,
12198      *       "animal_cards": [
12199      *         {
12200      *           "card_no": 1,
12201      *           "animal": "Lion"
12202      *         }
12203      *       ],
12204      *       "selected_cards": {
12205      *         "selected_card_1": 1,
12206      *         "selected_card_2": 2
12207      *       },
12208      *       "is_match": 1,
12209      *       "card_no": 1,
12210      *       "player_moves": 2,
12211      *       "players_profile": {
12212      *         "teacher": "TeacherName",
12213      *         "student": "StudentName"
12214      *       },
12215      *       "chat_hash": "abc123",
12216      *       "start_time": "2023-10-01 10:00:00",
12217      *       "teacher_id": 123,
12218      *       "user_id": 456
12219      *     }
12220      *   }
12221      * }
12222     * 
12223     * @apiErrorExample {js} Used in: Elements
12224     * Location: "view/Elements/chat_area_js.php"
12225     */
12226    public function gameDataMemcache(){
12227        $this->autoRender = false;
12228        $res = [
12229            'error' => true,
12230            'game_data' => []
12231        ];
12232
12233        if($this->request->is('post')){
12234            $data = $this->request->data;
12235            $game_chatHash = $data['chat_hash'];
12236
12237            // - use memcached
12238            if (!class_exists('myMemcached')) {
12239                App::uses('myMemcached', 'Lib');
12240            }
12241            $game_mem = new myMemcached();
12242            $game_mem_key = $game_chatHash;
12243            $game_cache = $game_mem->get($game_mem_key);
12244            $game_data = $game_cache ?? [];
12245            $res['error'] = false;
12246            $res['game_data'] = $game_data;
12247        }
12248
12249        return json_encode($res);
12250    }
12251    
12252    //only used in this controller
12253    public function getCurrentChatHash($modelName = null, $teacher_id){
12254        $chatHash = '';
12255    
12256        // Check if the model exists and is not null
12257        if (isset($this->$modelName)) {
12258            $getCurrentChatHash = $this->$modelName->find('first', array(
12259                'fields' => array('chat_hash'),
12260                'conditions' => array("$modelName.teacher_id" => $teacher_id),
12261            ));
12262            
12263            // Check if result is not empty before accessing its elements
12264            if (!empty($getCurrentChatHash[$modelName]['chat_hash'])) {
12265                $chatHash = $getCurrentChatHash[$modelName]['chat_hash'];
12266            }
12267        }
12268        
12269        return $chatHash;
12270    }
12271
12272    /**
12273     * @api {get} /teacher/api/googleCalendarCb googleCalendarCb()
12274     * @apiName googleCalendarCb
12275     * @apiGroup API
12276     * @apiDescription Google Calendar Callback
12277     * @apiSampleRequest off
12278     * 
12279     * @apiErrorExample {js} Return Type: View
12280     * Location: "view/Api/googleCalendarCb.php"
12281     */
12282    /**
12283    * GOOGLE INTEGRATION - SAVE GENERATED ACCESS TOKEN
12284    */ 
12285    public function googleCalendarCb(){
12286        $this->autoRender = false;
12287        $apiToken             = $this->Session->read('googleauthTeacherToken');
12288        $isMobappSreen         = $this->Session->read('isMobappScreen');
12289        $mobappTeacherId     = $this->Session->read('googleauthTeacherId');
12290        $teacherId             = !empty($mobappTeacherId) ? $mobappTeacherId : $this->Auth->user('id');
12291
12292        if( !empty($this->request->query['code']) ) {
12293            $google             = new GoogleCalendar();
12294            $code                 = $this->request->query['code'];
12295            $deviceType         = $this->request->query['state'];
12296            $myEnv                 = Configure::read('ENVIRONMENT');
12297            $redirectUri         = Configure::read('GOOGLE_CALENDAR_REDIRECT_URI.TEACHER');
12298            $myEnvRedirectUri     = $redirectUri[$myEnv];
12299            $data                 = $google->GetAccessToken(Configure::read('GOOGLE_CLIENT_ID_CALENDAR'), $myEnvRedirectUri, Configure::read('GOOGLE_CLIENT_SECRET_CALENDAR'), $code);
12300            $newAccessToken     = $data['access_token'];
12301            $newRefreshToken     = $data['refresh_token'];
12302
12303            if ( $deviceType == 0 || !empty($isMobappSreen) ) {
12304                require ROOT. "/teacher/Controller/GoogleCalendarSettingController.php";        
12305                $googleCalendarSetting = new GoogleCalendarSettingController();
12306                $response['success'] = $googleCalendarSetting->addGoogleAccessToken(array(
12307                    'teacher_id'             => $teacherId,
12308                    'google_access_token'     => $newAccessToken,
12309                    'google_refresh_token'     => $newRefreshToken
12310                ));
12311                if ( !empty($isMobappSreen) ) {
12312                    $this->Teacher->read(['google_calendar_flg'], $teacherId);
12313                    $this->Teacher->set(array(
12314                        'google_calendar_flg' => 1
12315                    ));
12316                    $this->Teacher->save();
12317                    return $this->redirect(myTools::getTeacherUrl() . "/mobapp/schedule?token={$apiToken}" );
12318                }
12319                return $this->redirect(myTools::getUrl() . '/teacher/home?calendarOAuth=1');
12320
12321            } else{
12322                $data = array(
12323                    'access_token' => $newAccessToken,
12324                    'refresh_token' => $newRefreshToken
12325                );
12326                $schemeUrl = 'nativecamp://view/google_calendar_success?'.http_build_query($data,'','&');
12327                $schemeUrl = urldecode($schemeUrl);
12328                $this->set('schemeUrl', $schemeUrl);
12329            }
12330        }
12331        $this->render('/Api/googleCalendarCb');
12332    }
12333
12334    /**
12335     * @api {post} /teacher/api/googleCalendarFlagUpdate googleCalendarFlagUpdate()
12336     * @apiName googleCalendarFlagUpdate
12337     * @apiGroup API
12338     * @apiDescription Update teacher google calendar synch flag
12339     * @apiSampleRequest off
12340     * 
12341     * @apiBody {Number} calendar_flag The flag to indicate if the calendar is synched.
12342     * 
12343     * @apiSuccess {Boolean} success Indicates if the operation was successful.
12344     * 
12345     * @apiErrorExample {json} Success-Response:
12346     *     {
12347     *       "success": true
12348     *     }
12349     *
12350     * @apiErrorExample {json} Error-Response:
12351     *     {
12352     *       "success": false
12353     *     }
12354     * 
12355     * @apiErrorExample {js} Used in: View
12356     * Location: "view/Announce/index.php"
12357     * Location: "view/Home/index.php"
12358     * Location: "view/Mobapp/index.php"
12359     */
12360    /**
12361    * Update teacher google calendar synch flag 
12362    * @return mixed - response
12363    */ 
12364    public function googleCalendarFlagUpdate() {
12365        $this->autoRender = false;
12366        $response = array(
12367            'success' => false
12368        );
12369        $mobappTeacherId     = $this->Session->read('googleauthTeacherId');
12370        $teacherId             = !empty($mobappTeacherId) ? $mobappTeacherId : $this->Auth->user('id');
12371        if (
12372            $this->request->is('ajax')
12373            && isset($this->request->data['calendar_flag'])
12374            && !empty($teacherId)
12375        ) {
12376            require ROOT. "/teacher/Controller/GoogleCalendarSettingController.php";        
12377            $googleCalendarSetting = new GoogleCalendarSettingController();
12378            $response['success'] = $googleCalendarSetting->googleCalendarFlagUpdate(array(
12379                'teacher_id' => $teacherId,
12380                'google_calendar_flag' => !empty($this->request->data['calendar_flag']) ? 1 : 0
12381            ));
12382        }
12383        return json_encode($response);
12384    }
12385
12386    /**
12387     * @api {get} /teacher/api/unlinkGoogleCalendar unlinkGoogleCalendar()
12388     * @apiName unlinkGoogleCalendar
12389     * @apiGroup API
12390     * @apiDescription Unlink google calendar
12391     * @apiSampleRequest off
12392     * 
12393     * @apiSuccess {Boolean} success Indicates if the operation was successful.
12394     * 
12395     * @apiErrorExample {json} Success-Response:
12396     *     {
12397     *       "success": true
12398     *     }
12399     *
12400     * @apiErrorExample {json} Error-Response:
12401     *     {
12402     *       "success": false
12403     *     }
12404     * 
12405     * @apiErrorExample {js} Used in: View
12406     * Location: "view/Announce/index.php"
12407     * Location: "view/Home/index.php"
12408     * Location: "view/Mobapp/index.php"
12409     * 
12410      * @apiErrorExample {js} Used in: JS
12411      * Location: "webroot/js/common.js"
12412     */
12413    /**
12414    * Unlink google calendar 
12415    * @return mixed - bool json_encode
12416    */
12417    public function unlinkGoogleCalendar() {
12418        $this->autoRender = false;
12419        $response = array(
12420            'success' => false
12421        );
12422        require ROOT. "/teacher/Controller/GoogleCalendarSettingController.php";        
12423        $googleCalendarSetting     = new GoogleCalendarSettingController();
12424        $mobappTeacherId         = $this->Session->read('googleauthTeacherId');
12425        $teacherId                 = !empty($mobappTeacherId) ? $mobappTeacherId : $this->Auth->user('id');
12426        $response['success']     = $googleCalendarSetting->unlinkGoogleCalendar(array(
12427            'teacher_id' => $teacherId
12428        ));
12429        return json_encode($response); 
12430    }
12431
12432    // NJ-27262 - check if user is a chocotto user
12433    private function isUserChocottoCamp($membershipIndex) {
12434        return in_array($membershipIndex, [
12435            Configure::read('membership_type_chocotto_plan_free'), 
12436            Configure::read('membership_type_chocotto_plan_paid')
12437        ]);
12438    }
12439    
12440    /**
12441     * @api {get} /teacher/api/getUploadRecordedFileAsync getUploadRecordedFileAsync()
12442     * @apiName getUploadRecordedFileAsync
12443     * @apiGroup API
12444     * @apiDescription uploads an audio file, optionally stores it in S3, and returns the result.
12445     *
12446     * @apiBody {File} audio The audio file to be uploaded.
12447     * @apiBody {String} [connect_id] The connect ID.
12448     * @apiBody {Boolean} [store_audio_record] Indicates if the audio record should be stored.
12449     * @apiBody {Number} [user_id] The user ID.
12450     *
12451     * @apiSuccess {Boolean} success Indicates if the operation was successful.
12452     * @apiSuccess {String} [audio_url] The URL of the stored audio file.
12453     * @apiSuccess {String} [textbook_name] The name of the textbook.
12454     * 
12455     * @apiErrorExample {json} Success-Response:
12456     *     {
12457     *       "success": true,
12458     *       "audio_url": "bucket_name/file_name.wav",
12459     *       "textbook_name": "Textbook Category : Textbook Name"
12460     *     }
12461     *
12462     * @apiSampleRequest off
12463     */
12464    /**
12465    * Save audio 
12466    * @return json $response
12467  */
12468    public function getUploadRecordedFileAsync() {
12469        $this->autoRender = false;
12470        $response = array(
12471            'success' => false,
12472            'convertedSpeech' => null,
12473            'config' => array()
12474        );
12475
12476        $uploader_id = $this->Auth->user('id');
12477
12478        // check if request has a valid file
12479        if (!isset($_FILES['audio']['size']) || !$_FILES['audio']['size']) {
12480            return json_encode($response);
12481        }
12482
12483        $connectId                 = !empty($this->request->data['connect_id']) ? $this->request->data['connect_id'] : null;
12484        $storedRecordingFlag     = !empty($this->request->data['store_audio_record']) ? true : false;
12485        $userId                    = !empty($this->request->data['user_id']) ? $this->request->data['user_id'] : null;
12486
12487        // Get audio temporary folder
12488        $audioContainer = ROOT . '/instructor/webroot/sound';
12489        if (!is_dir($audioContainer)) {
12490            mkdir($audioContainer);
12491        }
12492
12493        // move the file from temp name to local folder using $output name
12494        $input = $_FILES['audio']['tmp_name'];
12495        $fileName = $uploader_id . $_FILES['audio']['name'] . '.wav';
12496        $output = $audioContainer . '/' . $fileName;
12497
12498        // delete audio file if already exist
12499        if (file_exists($output)) {
12500            unlink($output);
12501        }
12502        // upload local file
12503        $moveFileSatus = move_uploaded_file($input, $output);
12504
12505        //-- Move audio recordings to s3 
12506        $this->loadModel('FileStorage');
12507        if ( $moveFileSatus && !empty($storedRecordingFlag) ) {
12508            $filename     = time() . uniqid() . '_' . basename($output);
12509            $tempResult = $this->FileStorage->uploadFile(array(
12510                'uploader_id'     => $this->Auth->user('id'),
12511                'uploader_type' => 34,
12512                'source'         => $output,
12513                'key'             => $filename,
12514                'file'             => $_FILES['audio'],
12515                'delete_image'     => true
12516            ));
12517            $fileUrl = $tempResult['FileStorage']['url'];
12518
12519            $response['audio_url'] = str_replace('https://', '', $fileUrl);
12520            $response['textbook_name'] = '';            
12521            //-- user current language
12522            $userLanguage = $this->User->fetchUserCurrentLanguage(array(
12523                'user_id' => $userId
12524            ));
12525            $textbookName = $this->TextbookConnect->getTextbookName(array(
12526                'connect_id' => $connectId,
12527                'native_language' => $userLanguage,
12528                'translate_other_lang' => true
12529            ));
12530
12531            $response['textbook_name'] .= !empty($textbookName['textbook_category_name']) ? $textbookName['textbook_category_name'] : '';
12532            $response['textbook_name'] .= !empty($textbookName['textbook_subcategory_name']) ? ' ' . $textbookName['textbook_subcategory_name'] : '';
12533            $response['textbook_name'] .= !empty($textbookName['textbook_name']) ? ' : ' . $textbookName['textbook_name'] : '';
12534            $response['success'] = true;
12535        }
12536
12537        // - delete audio file if already exist
12538        if (file_exists($output)) {
12539            unlink($output);
12540        }
12541
12542        return json_encode($response);
12543  }
12544
12545    /**
12546     * @api {get} /teacher/api/googleConvertedFileUpload googleConvertedFileUpload()
12547     * @apiName googleConvertedFileUpload
12548     * @apiGroup API
12549     * @apiDescription uploads an audio file, converts it to text using Google Cloud Storage, and returns the result.
12550     *
12551     * @apiBody {File} audio The audio file to be uploaded.
12552     *
12553     * @apiSuccess {Boolean} success Indicates if the operation was successful.
12554     * @apiSuccess {String} gsurl The Google Cloud Storage URL of the uploaded file.
12555     * @apiSuccess {String} filename The name of the uploaded file.
12556     * @apiSuccess {String} output The output file path.
12557     * @apiSuccess {String} convertedSpeech The converted speech text.
12558     * 
12559     * @apiErrorExample {json} Success-Response:
12560     *     {
12561     *       "success": true,
12562     *       "gsurl": "https://storage.googleapis.com/bucket_name/file_name.wav",
12563     *       "filename": "12345audio_name-1.wav",
12564     *       "output": "/path/to/output/file.wav",
12565     *       "convertedSpeech": "Translated text"
12566     *     }
12567     *
12568     * @apiSampleRequest off
12569     */
12570    public function googleConvertedFileUpload() {
12571        $this->autoRender = false;
12572        $response = array(
12573            'success' => false,
12574        );
12575
12576        $uploader_id = $this->Auth->user('id');
12577
12578        // check if request has a valid file
12579        if (!isset($_FILES['audio']['size']) || !$_FILES['audio']['size']) {
12580            return json_encode($response);
12581        }
12582
12583        // Get audio temporary folder
12584        $audioContainer = ROOT . '/instructor/webroot/sound';
12585        if (!is_dir($audioContainer)) {
12586            mkdir($audioContainer);
12587        }
12588
12589        // move the file from temp name to local folder using $output name
12590        $input = $_FILES['audio']['tmp_name'];
12591        $fileName = $uploader_id . $_FILES['audio']['name'] . '-1.wav';
12592        $output = $audioContainer . '/' . $fileName;
12593
12594        // delete audio file if already exist
12595        if (file_exists($output)) {
12596            unlink($output);
12597        }
12598        // upload local file
12599        $moveFileSatus = move_uploaded_file($input, $output);
12600            // query speech to text
12601        if ($moveFileSatus) {
12602            //uploaded successfully
12603            if (!class_exists('GoogleCloudStorage')) {
12604                App::import('Vendor', 'GoogleCloudStorage');
12605            }
12606            $gsURL = GoogleCloudStorage::upload_object($fileName, $output);
12607            $response = GoogleCloudStorage::fetchConvertAudioToSpeechAsync($gsURL);
12608            $response['success'] = true;
12609            $response['gsurl'] = $gsURL;
12610            $response['filename'] = $fileName;
12611            $response['output'] = $output;
12612        }
12613        return json_encode($response);
12614    }
12615
12616    /**
12617     * @api {get} /teacher/api/googleConvertedSpeech googleConvertedSpeech()
12618     * @apiName googleConvertedSpeech
12619     * @apiGroup API
12620     * @apiDescription converts speech to text using Google Cloud Storage and logs the results.
12621     *
12622     * @apiBody {String} convert_name The name of the file to be converted.
12623     * @apiBody {String} filename The name of the file.
12624     * @apiBody {String} output The output file path.
12625     *
12626     * @apiSuccess {Boolean} success Indicates if the operation was successful.
12627     * @apiSuccess {String} convertedSpeech The converted speech text.
12628     * @apiSuccess {String} totalTime The total time of the speech.
12629     * 
12630     * @apiErrorExample {json} Success-Response:
12631     *     {
12632     *       "success": true,
12633     *       "convertedSpeech": "Translated text",
12634     *       "totalTime": "10s"
12635     *     }
12636     *
12637     * @apiSampleRequest off
12638     */
12639    public function googleConvertedSpeech() {
12640        $this->autoRender = false;
12641        $response = array(
12642            'success' => false
12643        );
12644        $name = $this->request->data['convert_name'];
12645        $fileName = $this->request->data['filename'];
12646        $output = $this->request->data['output'];
12647        if (!class_exists('GoogleCloudStorage')) {
12648            App::import('Vendor', 'GoogleCloudStorage');
12649        }
12650        $response = GoogleCloudStorage::uploadRecordedFileWtResponse($name);
12651        if (isset($response['convertedSpeech']) && $response['convertedSpeech'] == "") {
12652                $response['convertedSpeech'] = '{"jpn" : "〔エラー〕:音声認識に問題があります。ヘッドセットあるいはイヤホンの着用/インターネット環境のご確認をお願いします","eng" : "Error : There is a problem with voice recognition. Please wear a headset or earphones and check your internet environment"}';
12653                $response['success'] = true;
12654        }
12655        if (isset($response['totalTime']) && !empty($response['totalTime'])) {
12656            $sum = str_replace('s', '', $response['totalTime']);
12657            //save speech to text logs
12658            $logParams = array(
12659                'service' => 2,
12660                'type' => 3,
12661                'controller' => static::class,
12662                'method' => __METHOD__,
12663                'url' => $this->request->here(),
12664                'value'    => number_format($sum, 2 , '.', '')
12665            );
12666            $googleLogs = ClassRegistry::init('GoogleTranslateLog');
12667            $result = $googleLogs->saveLog($logParams);        
12668            //check if logs saved
12669            if(!$result){
12670                $this->log('error', __METHOD__." -- [Google Translate] Unable to save logs. ");
12671            }
12672            // delete from google buckets
12673            GoogleCloudStorage::delete_object($fileName);
12674            
12675            $this->log("RESULTTTT: " . json_encode($response), 'debug');
12676            // - delete audio file if already exist
12677            if (file_exists($output)) {
12678                unlink($output);
12679            }
12680            $response['success'] = true;
12681        }    
12682        return json_encode($response);
12683    }
12684
12685    /*
12686     * Chivox get label translations
12687     * used in stress and intonation chivox textbooks
12688     * @param language (string) - required
12689     * @return res array
12690     */ 
12691    private function getLabelTranslations($language = 'en') {
12692        $result = [];
12693        $labelTranslations = array(
12694            'Correct' => array(
12695                'en' => 'Correct',
12696                'ja' => '正解',
12697                'zh-tw' => '正確',
12698                'ko' => '정답',
12699                'pt-br' => 'Correto',
12700                'th' => 'คำตอบที่ถูกต้อง',
12701                'vi' => 'câu trả lời đúng',
12702                'zh-cn' => '正确答案',
12703            ),
12704            'You' => array(
12705                'en' => 'You',
12706                'ja' => 'あなた',
12707                'zh-tw' => '你',
12708                'ko' => '당신',
12709                'pt-br' => 'Você',
12710                'th' => 'คุณ',
12711                'vi' => 'Bạn',
12712                'zh-cn' => '你',
12713            )
12714        );
12715
12716        $result['Correct'] = $labelTranslations['Correct'][$language] ?? $labelTranslations['Correct']['en'];
12717        $result['You'] = $labelTranslations['You'][$language] ?? $labelTranslations['You']['en'];
12718        return $result;
12719    }
12720
12721    /**
12722     * NJ-57841: Sends a Slack message to notify about a stream check error for a student.
12723     */
12724    public function sendSlackStreamCheckError() {
12725        $this->autoRender = false;
12726        
12727        $data = $this->request->data;
12728        $chatHash = $data['chatHash'] ?? '';
12729        $errorList = $data['errorList'] ?? [];
12730
12731        if($this->request->is('post') && !empty($chatHash)) {
12732            // - set slack message
12733            $mySlack = new mySlack();
12734            $mySlack->token = "xoxb-392902820692-6499139810404-RHHGc6Z5ZB9o2KkiGhzTTtlQ";
12735            $mySlack->username = "Student Stream Check - Invalid";
12736            $mySlack->channel = myTools::checkChannel("#bad-teacher-connections", "#bad-teacher-connections-dev");
12737            $mySlack->text = "";
12738            $mySlack->text .= "```";
12739            $mySlack->text .= "chat_hash: " . $chatHash;
12740            $mySlack->text .= "\nTeacher side JS errors: " . json_encode($errorList, true);
12741            $mySlack->text .= "```";
12742            
12743            // - send slack message
12744            $mySlack->postMessage();
12745        }
12746    }
12747
12748    /**
12749     * NJ-64043: check if chat hash exists in lesson onair table 
12750     **/
12751    public function lessonChatHashChecker() {
12752        $this->autoRender = false;
12753        $data = $this->request->query;
12754        $chatHash = $data['chat_hash'] ?? '';
12755        $return = ['exist' => false];
12756
12757        if(empty($chatHash)) {
12758            $this->log("Invalid chat hash: " . $chatHash, 'debug');
12759            return json_encode($return);
12760        }
12761
12762        $this->LessonOnair->openDBReplica();
12763        $chatHash = $this->LessonOnair->find('count', array(
12764            'conditions' => array(
12765                'chat_hash' => $chatHash,
12766                'teacher_id' => $this->Auth->user('id')
12767            ),
12768        ));
12769        $this->LessonOnair->closeDBReplica();
12770
12771        if ($chatHash) {
12772            $return['exist'] = true;
12773        }
12774
12775        return json_encode($return);
12776    }
12777}